rpi-calculator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +41 -0
- data/Rakefile +2 -0
- data/lib/crashing_the_dance/rpi_calculator.rb +50 -0
- data/lib/crashing_the_dance/rpi_calculator/opponent_game.rb +82 -0
- data/lib/crashing_the_dance/rpi_calculator/rpi.rb +94 -0
- data/lib/crashing_the_dance/rpi_calculator/version.rb +5 -0
- data/rpi-calculator.gemspec +26 -0
- data/spec/fixtures/games-palm.csv +23 -0
- data/spec/fixtures/teams-palm.csv +12 -0
- data/spec/fixtures/teams-single.csv +2 -0
- data/spec/lib/opponent_game_spec.rb +133 -0
- data/spec/lib/rpi_calculator_spec.rb +99 -0
- data/spec/spec_helper.rb +45 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 98536c60262dbcf1ddb66865bab77e55a1be56d5
|
4
|
+
data.tar.gz: b2b3e42adc026d2ea7738241f79b27cebee13dba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a7f566c210251d85fa576988d09fdecb0d5d07750d710ce22716e368767fa672e5d907034ee0cc73c2c45269538b8b0c56d981f2c63d4ce17353734199fdd8e7
|
7
|
+
data.tar.gz: 1168a73b2e3e12aaa1c02fd81596b82316d2ccce1dd35d67480bfd313a326e84ec76059518f41f2e983aac9e9036495f1c08a2c375f574d824637ae6248d25fb
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
vendor/bundle
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Andy Cox
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# RPI Calculator
|
2
|
+
|
3
|
+
Calculates the NCAA's college basketball Rating Percentage Index (a.k.a., RPI)
|
4
|
+
for a list of teams and games. This uses the current (as of the 2014-15 season)
|
5
|
+
version of the formula that was introduced in the 2004-05 season. It gives more
|
6
|
+
weight to home losses and road wins and less weight to road losses and home
|
7
|
+
wins.
|
8
|
+
|
9
|
+
For more information about the RPI, check out Jerry Palm's fantastic
|
10
|
+
[RPI FAQ](http://www.collegerpi.com/subs/rpifaq.html).
|
11
|
+
|
12
|
+
This is pre-release software, so it can and will change. Use at your own peril.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Note: Until the gem is published to RubyGems, you'll need to use the GitHub
|
17
|
+
repository ( https://github.com/crashingthedance/rpi-calculator ) as the source.
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
gem 'rpi-calculator'
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install rpi-calculator
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
TBD
|
34
|
+
|
35
|
+
## Contributing
|
36
|
+
|
37
|
+
1. Fork it ( https://github.com/crashingthedance/rpi-calculator/fork )
|
38
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
39
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
40
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
41
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "crashing_the_dance/rpi_calculator/version"
|
2
|
+
require "crashing_the_dance/rpi_calculator/rpi"
|
3
|
+
require "crashing_the_dance/rpi_calculator/opponent_game"
|
4
|
+
|
5
|
+
module CrashingTheDance
|
6
|
+
module RpiCalculator
|
7
|
+
# teams should be valid hash keys (#hash and #eql?)
|
8
|
+
# don't put any other requirements (e.g., #name)
|
9
|
+
# also use fixtures to test them. simple, lightweight.
|
10
|
+
def self.calculate(teams, games)
|
11
|
+
by_team = games_by_team(teams, games)
|
12
|
+
teams = calculate_rpi teams, by_team
|
13
|
+
calculate_owp teams, by_team
|
14
|
+
calculate_oowp teams
|
15
|
+
teams
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.calculate_oowp(teams)
|
21
|
+
teams.each do |team|
|
22
|
+
team.calculate_oowp teams
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.calculate_owp(teams, games_by_team)
|
27
|
+
teams.each do |team|
|
28
|
+
team.calculate_owp(games_by_team)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.calculate_rpi(teams, games_by_team)
|
33
|
+
teams.map { |team| RPI.new team, games_by_team[team] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.games_by_team(teams, games)
|
37
|
+
all = teams.inject({}) { |a, team| a[team] = [ ]; a }
|
38
|
+
games.each do |game|
|
39
|
+
# has to be a cleaner way than this
|
40
|
+
add_team_game all, game, game.vis_team
|
41
|
+
add_team_game all, game, game.home_team
|
42
|
+
end
|
43
|
+
all
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.add_team_game(all, game, team)
|
47
|
+
all[team] << OpponentGame.new(game, team) if all.has_key?(team)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module CrashingTheDance
|
2
|
+
module RpiCalculator
|
3
|
+
class OpponentGame
|
4
|
+
attr_reader :game, :team, :opponent, :score, :opponent_score
|
5
|
+
def initialize(game, team)
|
6
|
+
@game = game
|
7
|
+
@team = team
|
8
|
+
scores
|
9
|
+
end
|
10
|
+
|
11
|
+
def neutral?
|
12
|
+
game.neutral?
|
13
|
+
end
|
14
|
+
|
15
|
+
def win?
|
16
|
+
score > opponent_score
|
17
|
+
end
|
18
|
+
|
19
|
+
def loss?
|
20
|
+
score < opponent_score
|
21
|
+
end
|
22
|
+
|
23
|
+
def wins
|
24
|
+
win? ? 1 : 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def losses
|
28
|
+
loss? ? 1 : 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def rpi_wins
|
32
|
+
case
|
33
|
+
when loss? then 0.0
|
34
|
+
when home? then 0.6
|
35
|
+
when visitor? then 1.4
|
36
|
+
else 1.0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def rpi_losses
|
41
|
+
case
|
42
|
+
when win? then 0.0
|
43
|
+
when visitor? then 0.6
|
44
|
+
when home? then 1.4
|
45
|
+
else 1.0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def home?
|
50
|
+
@which_team == :home && !neutral?
|
51
|
+
end
|
52
|
+
|
53
|
+
def visitor?
|
54
|
+
@which_team == :visitor && !neutral?
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def scores
|
60
|
+
case
|
61
|
+
when team.eql?(game.vis_team)
|
62
|
+
@opponent = game.home_team
|
63
|
+
@which_team = :visitor
|
64
|
+
assign_scores(game.vis_score, game.home_score)
|
65
|
+
when team.eql?(game.home_team)
|
66
|
+
@opponent = game.vis_team
|
67
|
+
assign_scores(game.home_score, game.vis_score)
|
68
|
+
@which_team = :home
|
69
|
+
else
|
70
|
+
fail "Couldn't find #{team} in #{game}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def assign_scores(score, opponent_score)
|
75
|
+
fail "Both teams can't have the same score" if score == opponent_score
|
76
|
+
@score = score
|
77
|
+
@opponent_score = opponent_score
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module CrashingTheDance
|
2
|
+
module RpiCalculator
|
3
|
+
class RPI
|
4
|
+
attr_reader :team, :games, :wins, :losses, :rpi_wins, :rpi_losses
|
5
|
+
|
6
|
+
def initialize(team, games)
|
7
|
+
@team = team
|
8
|
+
@games = games
|
9
|
+
@owp = 0.0
|
10
|
+
@oowp = 0.0
|
11
|
+
@rpi = nil
|
12
|
+
calculate_record
|
13
|
+
end
|
14
|
+
|
15
|
+
def win_percentage
|
16
|
+
if games.count > 0
|
17
|
+
wins.to_f / (wins.to_f + losses.to_f)
|
18
|
+
else
|
19
|
+
0.0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def rpi_win_percentage
|
24
|
+
if games.count > 0
|
25
|
+
rpi_wins / (rpi_wins + rpi_losses)
|
26
|
+
else
|
27
|
+
0.0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def owp
|
32
|
+
@owp
|
33
|
+
end
|
34
|
+
|
35
|
+
def oowp
|
36
|
+
@oowp
|
37
|
+
end
|
38
|
+
|
39
|
+
def rpi
|
40
|
+
@rpi ||= calculate_rpi
|
41
|
+
end
|
42
|
+
|
43
|
+
# To calculate opponent's winning percentage (OWP), you remove games against
|
44
|
+
# the team in question.
|
45
|
+
# However, for the purpose of calculating OWP and OOWP, use standard WP.
|
46
|
+
def calculate_owp(games_by_team)
|
47
|
+
if games.count > 0
|
48
|
+
sum_owp = games.inject(0.0) do |sum, game|
|
49
|
+
sked = opponent_schedule game.opponent, games_by_team
|
50
|
+
ow = sked.inject(0) { |wins, og| wins + og.wins }
|
51
|
+
sum + (ow.to_f / sked.count.to_f)
|
52
|
+
end
|
53
|
+
@owp = sum_owp / games.count.to_f
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Now to calculate the opponents' opponents winning percentage (OOWP),
|
58
|
+
# you simply average the OWPs for each of their opponents.
|
59
|
+
def calculate_oowp(all_teams)
|
60
|
+
if games.count > 0
|
61
|
+
sum_oowp = games.inject(0.0) do |sum, game|
|
62
|
+
opponent = all_teams.find { |t| game.opponent.eql? t.team }
|
63
|
+
sum + opponent.owp
|
64
|
+
end
|
65
|
+
@oowp = sum_oowp / games.count.to_f
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def calculate_rpi
|
72
|
+
0.25 * rpi_win_percentage + 0.5 * owp + 0.25 * oowp
|
73
|
+
end
|
74
|
+
|
75
|
+
# filters out games against this team
|
76
|
+
def opponent_schedule(opponent, games_by_team)
|
77
|
+
all = games_by_team[opponent] || []
|
78
|
+
all.reject { |game| game.opponent.eql? team }
|
79
|
+
end
|
80
|
+
|
81
|
+
# w/l is commonly used, so can we extract it to a module?
|
82
|
+
def calculate_record
|
83
|
+
@wins = @rpi_wins = 0
|
84
|
+
@losses = @rpi_losses = 0
|
85
|
+
games.each do |game|
|
86
|
+
@wins += 1 if game.win?
|
87
|
+
@losses += 1 if game.loss?
|
88
|
+
@rpi_wins += game.rpi_wins
|
89
|
+
@rpi_losses += game.rpi_losses
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'crashing_the_dance/rpi_calculator/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rpi-calculator"
|
8
|
+
spec.version = CrashingTheDance::RpiCalculator::VERSION
|
9
|
+
spec.authors = ["Andy Cox"]
|
10
|
+
spec.email = ["andy@crashingthedance.com"]
|
11
|
+
spec.summary = %q{On your computer, calculatin' your RPI.}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = "http://crashingthedance.com"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.99"
|
24
|
+
spec.add_development_dependency "guard", "~> 0"
|
25
|
+
spec.add_development_dependency "guard-rspec", "~> 0"
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
vis_team,vis_score,home_team,home_score,neutral
|
2
|
+
Notre Dame,0,Valparaiso,1,
|
3
|
+
Valparaiso,1,Indiana,0,
|
4
|
+
Valparaiso,0,Purdue,1,
|
5
|
+
Michigan State,0,Notre Dame,1,
|
6
|
+
Notre Dame,0,Purdue,1
|
7
|
+
Notre Dame,0,IUPUI,1
|
8
|
+
Indiana,0,Kentucky,1
|
9
|
+
Indiana,0,Ball State,1
|
10
|
+
Duke,0,Purdue,1,N
|
11
|
+
UCLA,0,Purdue,1
|
12
|
+
IUPUI,0,Duke,1,N
|
13
|
+
Duke,1,IUPUI,0,
|
14
|
+
IUPUI,0,Duke,1,
|
15
|
+
IUPUI,0,Duke,1
|
16
|
+
IUPUI,0,UCLA,1
|
17
|
+
Ball State,0,UCLA,1
|
18
|
+
UCLA,1,Ball State,0
|
19
|
+
Kentucky,0,Ball State,1
|
20
|
+
Schmoo,0,Michigan State,1
|
21
|
+
Schmoo,0,Michigan State,1
|
22
|
+
Schmoo,0,Kentucky,1
|
23
|
+
Schmoo,0,Kentucky,1,N
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CrashingTheDance::RpiCalculator::OpponentGame do
|
4
|
+
context "typical game" do
|
5
|
+
before(:each) do
|
6
|
+
@visitor = Team.new "Visitor"
|
7
|
+
@home = Team.new "Home"
|
8
|
+
@game = Game.new(@visitor, @home, 66, 77, nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "visitor's perspective" do
|
12
|
+
before(:each) do
|
13
|
+
@wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new @game, @visitor
|
14
|
+
end
|
15
|
+
it "gets the self team right" do
|
16
|
+
expect(@wrapped.team).to eql(@visitor)
|
17
|
+
end
|
18
|
+
it "gets the self score right" do
|
19
|
+
expect(@wrapped.score).to eql(@game.vis_score)
|
20
|
+
end
|
21
|
+
it "gets the opponent right" do
|
22
|
+
expect(@wrapped.opponent).to eql(@home)
|
23
|
+
end
|
24
|
+
it "gets the opponent score right" do
|
25
|
+
expect(@wrapped.opponent_score).to eql(@game.home_score)
|
26
|
+
end
|
27
|
+
it "gets the site right" do
|
28
|
+
expect(@wrapped).not_to be_neutral
|
29
|
+
expect(@wrapped).to be_visitor
|
30
|
+
expect(@wrapped).not_to be_home
|
31
|
+
end
|
32
|
+
it "gets the win/loss right" do
|
33
|
+
expect(@wrapped).to be_loss
|
34
|
+
expect(@wrapped).not_to be_win
|
35
|
+
end
|
36
|
+
it "gets the RPI win/loss right" do
|
37
|
+
expect(@wrapped.rpi_wins).to be_within(0.01).of(0.0)
|
38
|
+
expect(@wrapped.rpi_losses).to be_within(0.01).of(0.6)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "home's perspective" do
|
43
|
+
before(:each) do
|
44
|
+
@wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new @game, @home
|
45
|
+
end
|
46
|
+
it "gets the self team right" do
|
47
|
+
expect(@wrapped.team).to eql(@home)
|
48
|
+
end
|
49
|
+
it "gets the self score right" do
|
50
|
+
expect(@wrapped.score).to eql(@game.home_score)
|
51
|
+
end
|
52
|
+
it "gets the opponent right" do
|
53
|
+
expect(@wrapped.opponent).to eql(@visitor)
|
54
|
+
end
|
55
|
+
it "gets the opponent score right" do
|
56
|
+
expect(@wrapped.opponent_score).to eql(@game.vis_score)
|
57
|
+
end
|
58
|
+
it "gets the site right" do
|
59
|
+
expect(@wrapped).not_to be_neutral
|
60
|
+
expect(@wrapped).to be_home
|
61
|
+
expect(@wrapped).not_to be_visitor
|
62
|
+
end
|
63
|
+
it "gets the win/loss right" do
|
64
|
+
expect(@wrapped).to be_win
|
65
|
+
expect(@wrapped).not_to be_loss
|
66
|
+
end
|
67
|
+
it "gets the RPI win/loss right" do
|
68
|
+
expect(@wrapped.rpi_wins).to be_within(0.01).of(0.6)
|
69
|
+
expect(@wrapped.rpi_losses).to be_within(0.01).of(0.0)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
# these test the spec Game more than the real one
|
76
|
+
context "neutral game scenarios" do
|
77
|
+
before(:each) do
|
78
|
+
@visitor = Team.new "Visitor"
|
79
|
+
@home = Team.new "Home"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "treats nil values as non-neutral" do
|
83
|
+
game = Game.new(@visitor, @home, 66, 77, nil)
|
84
|
+
wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new game, @visitor
|
85
|
+
expect(wrapped).not_to be_neutral
|
86
|
+
end
|
87
|
+
|
88
|
+
it "treats empty strings as non-neutral" do
|
89
|
+
game = Game.new(@visitor, @home, 66, 77, '')
|
90
|
+
wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new game, @visitor
|
91
|
+
expect(wrapped).not_to be_neutral
|
92
|
+
end
|
93
|
+
|
94
|
+
it "treats blank strings as non-neutral" do
|
95
|
+
game = Game.new(@visitor, @home, 66, 77, ' ')
|
96
|
+
wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new game, @visitor
|
97
|
+
expect(wrapped).not_to be_neutral
|
98
|
+
end
|
99
|
+
|
100
|
+
it "treats 'N' as neutral" do
|
101
|
+
game = Game.new(@visitor, @home, 66, 77, 'N')
|
102
|
+
wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new game, @visitor
|
103
|
+
expect(wrapped).to be_neutral
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "neutral game RPI" do
|
108
|
+
before(:each) do
|
109
|
+
@visitor = Team.new "Visitor"
|
110
|
+
@home = Team.new "Home"
|
111
|
+
@game = Game.new(@visitor, @home, 66, 77, 'N')
|
112
|
+
end
|
113
|
+
|
114
|
+
it "gets the site right" do
|
115
|
+
wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new @game, @visitor
|
116
|
+
expect(wrapped).to be_neutral
|
117
|
+
expect(wrapped).not_to be_home
|
118
|
+
expect(wrapped).not_to be_visitor
|
119
|
+
end
|
120
|
+
|
121
|
+
it "gets the loser RPI wins/loss right" do
|
122
|
+
wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new @game, @visitor
|
123
|
+
expect(wrapped.rpi_wins).to be_within(0.01).of(0.0)
|
124
|
+
expect(wrapped.rpi_losses).to be_within(0.01).of(1.0)
|
125
|
+
end
|
126
|
+
it "gets the winner RPI wins/loss right" do
|
127
|
+
wrapped = CrashingTheDance::RpiCalculator::OpponentGame.new @game, @home
|
128
|
+
expect(wrapped.rpi_wins).to be_within(0.01).of(1.0)
|
129
|
+
expect(wrapped.rpi_losses).to be_within(0.01).of(0.0)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "RPI calculator" do
|
4
|
+
context "default" do
|
5
|
+
it "calculates the RPI" do
|
6
|
+
teams = []
|
7
|
+
games = []
|
8
|
+
rpi = CrashingTheDance::RpiCalculator.calculate teams, games
|
9
|
+
|
10
|
+
expect(teams.size).to eql(teams.size)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "returns your custom team attributes" do
|
14
|
+
id = 1
|
15
|
+
teams = teams_fixture 'single', id
|
16
|
+
games = []
|
17
|
+
rpi = CrashingTheDance::RpiCalculator.calculate teams, games
|
18
|
+
|
19
|
+
expect(teams.first.id).to eql(id)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "no games played" do
|
24
|
+
# do this and clean up the code
|
25
|
+
before(:each) do
|
26
|
+
@teams = teams_fixture 'palm'
|
27
|
+
games = []
|
28
|
+
@rpi = CrashingTheDance::RpiCalculator.calculate @teams, games
|
29
|
+
end
|
30
|
+
|
31
|
+
it "calculates the right number of teams" do
|
32
|
+
expect(@rpi.size).to eql(@teams.size)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "calculates the right win/loss record" do
|
36
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
37
|
+
expect(valpo.wins).to eq(0)
|
38
|
+
expect(valpo.losses).to eq(0)
|
39
|
+
expect(valpo.win_percentage).to be_within(0.0001).of(0.0000)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "calculates the RPI OWP" do
|
43
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
44
|
+
expect(valpo.owp).to be_within(0.0001).of(0.0)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "calculates the RPI OOWP" do
|
48
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
49
|
+
expect(valpo.oowp).to be_within(0.0001).of(0.0)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "calculates the RPI" do
|
53
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
54
|
+
expect(valpo.rpi).to be_within(0.0001).of(0.0)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# This comes from Jerry Palm's example RPI scenario at
|
59
|
+
# http://www.collegerpi.com/subs/rpifaq.html
|
60
|
+
context "the Palm scenario" do
|
61
|
+
before(:each) do
|
62
|
+
@teams = teams_fixture 'palm'
|
63
|
+
games = games_fixture 'palm'
|
64
|
+
@rpi = CrashingTheDance::RpiCalculator.calculate @teams, games
|
65
|
+
end
|
66
|
+
|
67
|
+
it "calculates the right win/loss record" do
|
68
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
69
|
+
expect(valpo.wins).to eq(2)
|
70
|
+
expect(valpo.losses).to eq(1)
|
71
|
+
expect(valpo.win_percentage).to be_within(0.0001).of(0.6667)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "calculates the RPI win/loss record" do
|
75
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
76
|
+
expect(valpo.rpi_wins).to be_within(0.0001).of(2.0)
|
77
|
+
expect(valpo.rpi_losses).to be_within(0.0001).of(0.6)
|
78
|
+
expect(valpo.rpi_win_percentage).to be_within(0.0001).of(0.7692)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "calculates the RPI OWP" do
|
82
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
83
|
+
expect(valpo.owp).to be_within(0.0001).of(0.4444)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "calculates the RPI OOWP" do
|
87
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
88
|
+
expect(valpo.oowp).to be_within(0.0001).of(0.6528)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "calculates the RPI" do
|
92
|
+
valpo = @rpi.find { |team| team.team.name.eql? "Valparaiso" }
|
93
|
+
expect(valpo.rpi).to be_within(0.0001).of(0.5777)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "full season" do
|
98
|
+
end
|
99
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'crashing_the_dance/rpi_calculator'
|
3
|
+
|
4
|
+
Team = Struct.new(:name, :id) do
|
5
|
+
def eql?(other)
|
6
|
+
name.eql?(other.name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def hash
|
10
|
+
name.hash
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Game = Struct.new(:vis_team, :home_team, :vis_score, :home_score, :neutral) do
|
15
|
+
def neutral?
|
16
|
+
!neutral.nil? && neutral == "N"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def games_fixture(which)
|
21
|
+
file = File.join(File.dirname(__FILE__), "fixtures", "games-#{which}.csv")
|
22
|
+
teams = teams_fixture which
|
23
|
+
games = []
|
24
|
+
CSV.foreach(file, :headers => true) do |row|
|
25
|
+
#puts "It's a game: #{row}"
|
26
|
+
#puts "teams #{teams}"
|
27
|
+
next if row.nil? || row.empty?
|
28
|
+
games << Game.new(teams.find { |team| team.name.eql?(row["vis_team"]) },
|
29
|
+
teams.find { |team| team.name.eql?(row["home_team"]) },
|
30
|
+
row["vis_score"].to_i,
|
31
|
+
row["home_score"].to_i,
|
32
|
+
row["neutral"])
|
33
|
+
end
|
34
|
+
games
|
35
|
+
end
|
36
|
+
|
37
|
+
def teams_fixture(which, id = 1)
|
38
|
+
file = File.join(File.dirname(__FILE__), "fixtures", "teams-#{which}.csv")
|
39
|
+
teams = []
|
40
|
+
CSV.foreach(file, :headers => true) do |team|
|
41
|
+
teams << Team.new(team["name"], id) unless team.nil? || team["name"].nil? || team["name"].empty?
|
42
|
+
id += 1
|
43
|
+
end
|
44
|
+
teams
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rpi-calculator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andy Cox
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.99'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.99'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: ''
|
84
|
+
email:
|
85
|
+
- andy@crashingthedance.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- lib/crashing_the_dance/rpi_calculator.rb
|
96
|
+
- lib/crashing_the_dance/rpi_calculator/opponent_game.rb
|
97
|
+
- lib/crashing_the_dance/rpi_calculator/rpi.rb
|
98
|
+
- lib/crashing_the_dance/rpi_calculator/version.rb
|
99
|
+
- rpi-calculator.gemspec
|
100
|
+
- spec/fixtures/games-palm.csv
|
101
|
+
- spec/fixtures/teams-palm.csv
|
102
|
+
- spec/fixtures/teams-single.csv
|
103
|
+
- spec/lib/opponent_game_spec.rb
|
104
|
+
- spec/lib/rpi_calculator_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
homepage: http://crashingthedance.com
|
107
|
+
licenses:
|
108
|
+
- MIT
|
109
|
+
metadata: {}
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 2.2.2
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: On your computer, calculatin' your RPI.
|
130
|
+
test_files:
|
131
|
+
- spec/fixtures/games-palm.csv
|
132
|
+
- spec/fixtures/teams-palm.csv
|
133
|
+
- spec/fixtures/teams-single.csv
|
134
|
+
- spec/lib/opponent_game_spec.rb
|
135
|
+
- spec/lib/rpi_calculator_spec.rb
|
136
|
+
- spec/spec_helper.rb
|