sciolyff-duosmium 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +92 -0
- data/bin/sciolyff +38 -0
- data/lib/sciolyff.rb +9 -0
- data/lib/sciolyff/interpreter.rb +89 -0
- data/lib/sciolyff/interpreter/bids.rb +24 -0
- data/lib/sciolyff/interpreter/event.rb +69 -0
- data/lib/sciolyff/interpreter/html.rb +65 -0
- data/lib/sciolyff/interpreter/html/helpers.rb +230 -0
- data/lib/sciolyff/interpreter/html/main.css +1 -0
- data/lib/sciolyff/interpreter/html/main.js +74 -0
- data/lib/sciolyff/interpreter/html/template.html.erb +480 -0
- data/lib/sciolyff/interpreter/model.rb +29 -0
- data/lib/sciolyff/interpreter/penalty.rb +19 -0
- data/lib/sciolyff/interpreter/placing.rb +127 -0
- data/lib/sciolyff/interpreter/raw.rb +50 -0
- data/lib/sciolyff/interpreter/subdivisions.rb +72 -0
- data/lib/sciolyff/interpreter/team.rb +111 -0
- data/lib/sciolyff/interpreter/tiebreaks.rb +34 -0
- data/lib/sciolyff/interpreter/tournament.rb +194 -0
- data/lib/sciolyff/validator.rb +89 -0
- data/lib/sciolyff/validator/canonical.rb +23 -0
- data/lib/sciolyff/validator/checker.rb +33 -0
- data/lib/sciolyff/validator/events.rb +106 -0
- data/lib/sciolyff/validator/logger.rb +48 -0
- data/lib/sciolyff/validator/penalties.rb +19 -0
- data/lib/sciolyff/validator/placings.rb +136 -0
- data/lib/sciolyff/validator/range.rb +15 -0
- data/lib/sciolyff/validator/raws.rb +40 -0
- data/lib/sciolyff/validator/sections.rb +56 -0
- data/lib/sciolyff/validator/subdivisions.rb +64 -0
- data/lib/sciolyff/validator/teams.rb +108 -0
- data/lib/sciolyff/validator/top_level.rb +23 -0
- data/lib/sciolyff/validator/tournament.rb +138 -0
- data/sciolyff.gemspec +22 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9f8d723a4b5265cf85a60231c83d8f1eeea0558cc0f6c8f2e51572bca2a86e6f
|
4
|
+
data.tar.gz: b733acad947c8c180011279e4681c086bc94d78efb920433f6e0d72333c3b8e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 62fddbdf4bb9e9e953678a9cf87487c9f68b97b082cc9be825f384933ae06093923dfbc1966aa7c1a5f8ff7fd60107216f43ef9014fb09d5d57b6eba9fff76cb
|
7
|
+
data.tar.gz: beef2bd6cad0cf430f7699b515a85459881ef1c3f4602ef622046e2f62024d52917eafe63418aa27825ea8dc41c5aa1601b57c327b8376456940c001e17816cf
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2020 Em Zhan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# SciolyFF (Science Olympiad File Format)
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/sciolyff.svg)](https://badge.fury.io/rb/sciolyff)
|
4
|
+
|
5
|
+
We propose a standardized file format called SciolyFF to represent Science
|
6
|
+
Olympiad tournament results. This will allow for a more universal record of
|
7
|
+
tournament performance and also make it easier to do sabermetric-like stats and
|
8
|
+
other fun stuff. The format is a subset of YAML for easy implementation of
|
9
|
+
parsers across many programming languages.
|
10
|
+
|
11
|
+
A website that generates results tables based off SciolyFF files can be found
|
12
|
+
[here](https://unosmium.org/results/) and the source code for the website
|
13
|
+
[here](https://github.com/unosmium/unosmium.org).
|
14
|
+
|
15
|
+
## Specification
|
16
|
+
|
17
|
+
Reading through the demo file [here](examples/demo.yaml) is probably the fastest
|
18
|
+
way to get acquainted with the format. Officially, any file that passes the
|
19
|
+
validation (see Usage -- Validation) is valid, but the intentions of the format
|
20
|
+
outlined in the comments of the demo file should be respected.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
```
|
25
|
+
gem install sciolyff
|
26
|
+
```
|
27
|
+
|
28
|
+
This gem is currently in an alpha stage. To get the latest changes before
|
29
|
+
official releases, build from source:
|
30
|
+
|
31
|
+
```
|
32
|
+
git clone https://github.com/unosmium/sciolyff.git && cd sciolyff
|
33
|
+
gem build sciolyff.gemspec
|
34
|
+
gem install ./sciolyff-0.12.0.gem
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
### Validation
|
40
|
+
|
41
|
+
A Ruby gem provided in this repository contains a command line utility that can
|
42
|
+
validate if a given file meets the SciolyFF. The files found in
|
43
|
+
`lib/sciolyff/validator` also serve as the specification for the format.
|
44
|
+
|
45
|
+
From the command line, e.g.
|
46
|
+
|
47
|
+
```
|
48
|
+
sciolyff 2017-05-20_nationals_c.yaml
|
49
|
+
```
|
50
|
+
|
51
|
+
Inside Ruby code, e.g.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'sciolyff'
|
55
|
+
|
56
|
+
validator = SciolyFF::Validator.new
|
57
|
+
puts validator.valid? File.read('2017-05-20_nationals_c.yaml') #=> true
|
58
|
+
print validator.last_log #=> error or warning messages
|
59
|
+
```
|
60
|
+
|
61
|
+
### Parsing
|
62
|
+
|
63
|
+
Although the SciolyFF makes the results file human-readable without the
|
64
|
+
ambiguity of spreadsheet results, it can be a bit awkward to parse overall
|
65
|
+
results -- for instance, when trying to regenerate a results spreadsheet from a
|
66
|
+
SciolyFF file.
|
67
|
+
|
68
|
+
To make this easier, a `SciolyFF::Interpreter` class has been provided to wrap
|
69
|
+
the output of Ruby's yaml parser. For example:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
require 'sciolyff'
|
73
|
+
|
74
|
+
i = SciolyFF::Interpreter.new(File.read('2017-05-20_nationals_c.yaml'))
|
75
|
+
|
76
|
+
a_and_p = i.events.find { |e| e.name == 'Anatomy and Physiology' }
|
77
|
+
a_and_p.trialed? #=> false
|
78
|
+
|
79
|
+
team_one = i.teams.find { |t| t.number == 1 }
|
80
|
+
team_one.placing_for(a_and_p).points #=> 7
|
81
|
+
team_one.points #=> 448
|
82
|
+
|
83
|
+
# sorted by rank
|
84
|
+
i.teams #=> [#<...{:school=>"Troy H.S.", :number=>3, :state=>"CA"}>, ... ]
|
85
|
+
```
|
86
|
+
|
87
|
+
A fuller example can be found here in the code for the Unosmium Results website,
|
88
|
+
found
|
89
|
+
[here](https://github.com/unosmium/unosmium.org/blob/master/source/results/template.html.erb).
|
90
|
+
There is also of course the
|
91
|
+
[documentation](https://www.rubydoc.info/gems/sciolyff/0.12.0), a bit sparse
|
92
|
+
currently.
|
data/bin/sciolyff
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'optimist'
|
5
|
+
require 'sciolyff'
|
6
|
+
|
7
|
+
opts = Optimist.options do
|
8
|
+
version 'sciolyff 0.13.0'
|
9
|
+
banner <<~STRING
|
10
|
+
Checks if a given file is in the Scioly File Format
|
11
|
+
|
12
|
+
Usage:
|
13
|
+
#{File.basename(__FILE__)} [options] <file>
|
14
|
+
|
15
|
+
where [options] are:
|
16
|
+
STRING
|
17
|
+
opt :loglevel, 'Log verbosity from 0 to 3', default: 1
|
18
|
+
opt :nocanon, 'Disable canonical name checks'
|
19
|
+
end
|
20
|
+
|
21
|
+
Optimist.educate if ARGV.empty?
|
22
|
+
puts 'More than one file given, ignoring all but first.' if ARGV.length > 1
|
23
|
+
|
24
|
+
begin
|
25
|
+
contents = File.read(ARGV.first)
|
26
|
+
rescue StandardError => e
|
27
|
+
puts "Could not read file: #{e}"
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
|
31
|
+
validator = SciolyFF::Validator.new(
|
32
|
+
loglevel: opts[:loglevel],
|
33
|
+
canonical: !opts[:nocanon]
|
34
|
+
)
|
35
|
+
validity = validator.valid? contents
|
36
|
+
print validator.last_log
|
37
|
+
|
38
|
+
exit validity
|
data/lib/sciolyff.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SciolyFF
|
4
|
+
# Interprets the YAML representation of a SciolyFF file through objects that
|
5
|
+
# respond to idiomatic Ruby method calls
|
6
|
+
class Interpreter
|
7
|
+
require 'sciolyff/interpreter/tournament'
|
8
|
+
require 'sciolyff/interpreter/event'
|
9
|
+
require 'sciolyff/interpreter/team'
|
10
|
+
require 'sciolyff/interpreter/placing'
|
11
|
+
require 'sciolyff/interpreter/penalty'
|
12
|
+
|
13
|
+
require 'sciolyff/interpreter/tiebreaks'
|
14
|
+
require 'sciolyff/interpreter/subdivisions'
|
15
|
+
require 'sciolyff/interpreter/html'
|
16
|
+
|
17
|
+
attr_reader :tournament, :events, :teams, :placings, :penalties
|
18
|
+
|
19
|
+
def initialize(rep)
|
20
|
+
if rep.instance_of? String
|
21
|
+
rep = Psych.safe_load(rep,
|
22
|
+
permitted_classes: [Date],
|
23
|
+
symbolize_names: true)
|
24
|
+
end
|
25
|
+
create_models(@rep = rep)
|
26
|
+
link_models(self)
|
27
|
+
|
28
|
+
sort_events_naturally
|
29
|
+
sort_teams_by_rank
|
30
|
+
end
|
31
|
+
|
32
|
+
def subdivisions
|
33
|
+
@subdivisions ||=
|
34
|
+
teams.map(&:subdivision)
|
35
|
+
.uniq
|
36
|
+
.compact
|
37
|
+
.map { |sub| [sub, Interpreter.new(subdivision_rep(sub))] }
|
38
|
+
.to_h
|
39
|
+
end
|
40
|
+
|
41
|
+
def raws?
|
42
|
+
placings.any?(&:raw?)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def create_models(rep)
|
48
|
+
@tournament = Tournament.new(rep)
|
49
|
+
@events = map_array_to_models rep[:Events], Event, rep
|
50
|
+
@teams = map_array_to_models rep[:Teams], Team, rep
|
51
|
+
@placings = map_array_to_models rep[:Placings], Placing, rep
|
52
|
+
@penalties = map_array_to_models rep[:Penalties], Penalty, rep
|
53
|
+
end
|
54
|
+
|
55
|
+
def map_array_to_models(arr, object_class, rep)
|
56
|
+
return [] if arr.nil?
|
57
|
+
|
58
|
+
arr.map.with_index { |_, index| object_class.new(rep, index) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def link_models(interpreter)
|
62
|
+
# models have to linked in reverse order because reasons
|
63
|
+
@penalties.each { |m| m.link_to_other_models(interpreter) }
|
64
|
+
@placings .each { |m| m.link_to_other_models(interpreter) }
|
65
|
+
@teams .each { |m| m.link_to_other_models(interpreter) }
|
66
|
+
@events .each { |m| m.link_to_other_models(interpreter) }
|
67
|
+
@tournament.link_to_other_models(interpreter)
|
68
|
+
end
|
69
|
+
|
70
|
+
def sort_events_naturally
|
71
|
+
@events.sort_by! { |e| [e.trial?.to_s, e.name] }
|
72
|
+
end
|
73
|
+
|
74
|
+
def sort_teams_by_rank
|
75
|
+
sorted =
|
76
|
+
@teams
|
77
|
+
.group_by { |t| [t.disqualified?.to_s, t.exhibition?.to_s] }
|
78
|
+
.map { |key, teams| [key, sort_teams_by_points(teams)] }
|
79
|
+
.sort_by(&:first)
|
80
|
+
.map(&:last)
|
81
|
+
.flatten
|
82
|
+
@teams.map!.with_index { |_, i| sorted[i] }
|
83
|
+
end
|
84
|
+
|
85
|
+
include Interpreter::Tiebreaks
|
86
|
+
include Interpreter::Subdivisions
|
87
|
+
include Interpreter::HTML
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SciolyFF
|
4
|
+
# Bid assignment logic, to be included in the Interpreter::Tournament class
|
5
|
+
module Interpreter::Bids
|
6
|
+
def top_teams_per_school
|
7
|
+
# explicitly relies on uniq traversing in order
|
8
|
+
@top_teams_per_school ||= @teams.uniq { |t| [t.school, t.city, t.state] }
|
9
|
+
end
|
10
|
+
|
11
|
+
def teams_eligible_for_bids
|
12
|
+
return top_teams_per_school if bids_per_school == 1
|
13
|
+
|
14
|
+
# doesn't rely on group_by preserving order
|
15
|
+
@teams_eligible_for_bids ||=
|
16
|
+
@teams
|
17
|
+
.group_by { |t| [t.school, t.city, t.state] }
|
18
|
+
.each_value { |teams| teams.sort_by!(&:rank) }
|
19
|
+
.map { |_, teams| teams.take(bids_per_school) }
|
20
|
+
.flatten
|
21
|
+
.sort_by(&:rank)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sciolyff/interpreter/model'
|
4
|
+
|
5
|
+
module SciolyFF
|
6
|
+
# Models an instance of a Science Olympiad event at a specific tournament
|
7
|
+
class Interpreter::Event < Interpreter::Model
|
8
|
+
def link_to_other_models(interpreter)
|
9
|
+
super
|
10
|
+
@placings = interpreter.placings.select { |p| p.event == self }
|
11
|
+
@placings_by_team =
|
12
|
+
@placings.group_by(&:team).transform_values!(&:first)
|
13
|
+
@raws = @placings.select(&:raw?).map(&:raw).sort
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :placings, :raws
|
17
|
+
|
18
|
+
def name
|
19
|
+
@rep[:name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def trial?
|
23
|
+
@rep[:trial] || false
|
24
|
+
end
|
25
|
+
|
26
|
+
def trialed?
|
27
|
+
@rep[:trialed] || false
|
28
|
+
end
|
29
|
+
|
30
|
+
def high_score_wins?
|
31
|
+
!low_score_wins?
|
32
|
+
end
|
33
|
+
|
34
|
+
def low_score_wins?
|
35
|
+
@rep[:scoring] == 'low'
|
36
|
+
end
|
37
|
+
|
38
|
+
def placing_for(team)
|
39
|
+
@placings_by_team[team]
|
40
|
+
end
|
41
|
+
|
42
|
+
def maximum_place
|
43
|
+
@maximum_place ||=
|
44
|
+
if trial?
|
45
|
+
placings.size
|
46
|
+
elsif tournament.per_event_n?
|
47
|
+
[per_event_maximum_place, tournament.maximum_place].min
|
48
|
+
else
|
49
|
+
tournament.maximum_place
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def per_event_maximum_place
|
56
|
+
return competing_teams_count if tournament.per_event_n == 'participation'
|
57
|
+
|
58
|
+
placings.map(&:place).compact.max + 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def competing_teams_count
|
62
|
+
return placings.count(&:participated?) if trial?
|
63
|
+
|
64
|
+
placings.count do |p|
|
65
|
+
p.participated? && !(p.team.exhibition? || p.exempt?)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SciolyFF
|
4
|
+
# Grants ability to convert a SciolyFF file into stand-alone HTML and other
|
5
|
+
# formats (YAML, JSON)
|
6
|
+
module Interpreter::HTML
|
7
|
+
require 'erubi'
|
8
|
+
require 'sciolyff/interpreter/html/helpers'
|
9
|
+
require 'json'
|
10
|
+
|
11
|
+
def to_html(hide_raw: false, color: '#000000')
|
12
|
+
helpers = Interpreter::HTML::Helpers.new
|
13
|
+
src = Erubi::Engine.new(helpers.template).src
|
14
|
+
helpers.eval_with_binding(src, self, hide_raw, color)
|
15
|
+
.gsub(/^\s*$/, '') # remove empty lines
|
16
|
+
.gsub(/\s+$/, '') # remove trailing whitespace
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_yaml(hide_raw: false)
|
20
|
+
rep = hide_raw ? hidden_raw_rep : @rep
|
21
|
+
stringify_keys(rep).to_yaml
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_json(hide_raw: false, pretty: false)
|
25
|
+
rep = hide_raw ? hidden_raw_rep : @rep
|
26
|
+
return JSON.pretty_generate(rep) if pretty
|
27
|
+
|
28
|
+
rep.to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def hidden_raw_rep
|
34
|
+
@hidden_raw_rep || begin
|
35
|
+
@hidden_raw_rep = Marshal.load(Marshal.dump(@rep))
|
36
|
+
@hidden_raw_rep[:Placings].each { |p| hide_and_replace_raw(p) }
|
37
|
+
@hidden_raw_rep
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def hide_and_replace_raw(placing_rep)
|
42
|
+
placing = placings.find do |p|
|
43
|
+
p.event.name == placing_rep[:event] &&
|
44
|
+
p.team.number == placing_rep[:team]
|
45
|
+
end
|
46
|
+
placing_rep.delete :raw
|
47
|
+
placing_rep[:tie] = true if placing.tie?
|
48
|
+
placing_rep[:place] = placing.place if placing.place
|
49
|
+
end
|
50
|
+
|
51
|
+
def stringify_keys(hash)
|
52
|
+
return hash unless hash.instance_of? Hash
|
53
|
+
|
54
|
+
hash.map do |k, v|
|
55
|
+
new_k = k.to_s
|
56
|
+
new_v = case v
|
57
|
+
when Array then v.map { |e| stringify_keys(e) }
|
58
|
+
when Hash then stringify_keys(v)
|
59
|
+
else v
|
60
|
+
end
|
61
|
+
[new_k, new_v]
|
62
|
+
end.to_h
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SciolyFF
|
4
|
+
# Holds helper methods used in template.html.erb
|
5
|
+
class Interpreter::HTML::Helpers
|
6
|
+
def template
|
7
|
+
File.read(File.join(__dir__, 'template.html.erb'))
|
8
|
+
end
|
9
|
+
|
10
|
+
def eval_with_binding(src, interpreter, hide_raw, color)
|
11
|
+
i = interpreter
|
12
|
+
css_file_content = File.read(File.join(__dir__, 'main.css'))
|
13
|
+
js_file_content = File.read(File.join(__dir__, 'main.js'))
|
14
|
+
eval(src)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
STATES_BY_POSTAL_CODE = {
|
20
|
+
AK: 'Alaska',
|
21
|
+
AZ: 'Arizona',
|
22
|
+
AR: 'Arkansas',
|
23
|
+
CA: 'California',
|
24
|
+
nCA: 'Northern California',
|
25
|
+
sCA: 'Southern California',
|
26
|
+
CO: 'Colorado',
|
27
|
+
CT: 'Connecticut',
|
28
|
+
DE: 'Delaware',
|
29
|
+
DC: 'District of Columbia',
|
30
|
+
FL: 'Florida',
|
31
|
+
GA: 'Georgia',
|
32
|
+
HI: 'Hawaii',
|
33
|
+
ID: 'Idaho',
|
34
|
+
IL: 'Illinois',
|
35
|
+
IN: 'Indiana',
|
36
|
+
IA: 'Iowa',
|
37
|
+
KS: 'Kansas',
|
38
|
+
KY: 'Kentucky',
|
39
|
+
LA: 'Louisiana',
|
40
|
+
ME: 'Maine',
|
41
|
+
MD: 'Maryland',
|
42
|
+
MA: 'Massachusetts',
|
43
|
+
MI: 'Michigan',
|
44
|
+
MN: 'Minnesota',
|
45
|
+
MS: 'Mississippi',
|
46
|
+
MO: 'Missouri',
|
47
|
+
MT: 'Montana',
|
48
|
+
NE: 'Nebraska',
|
49
|
+
NV: 'Nevada',
|
50
|
+
NH: 'New Hampshire',
|
51
|
+
NJ: 'New Jersey',
|
52
|
+
NM: 'New Mexico',
|
53
|
+
NY: 'New York',
|
54
|
+
NC: 'North Carolina',
|
55
|
+
ND: 'North Dakota',
|
56
|
+
OH: 'Ohio',
|
57
|
+
OK: 'Oklahoma',
|
58
|
+
OR: 'Oregon',
|
59
|
+
PA: 'Pennsylvania',
|
60
|
+
RI: 'Rhode Island',
|
61
|
+
SC: 'South Carolina',
|
62
|
+
SD: 'South Dakota',
|
63
|
+
TN: 'Tennessee',
|
64
|
+
TX: 'Texas',
|
65
|
+
UT: 'Utah',
|
66
|
+
VT: 'Vermont',
|
67
|
+
VA: 'Virginia',
|
68
|
+
WA: 'Washington',
|
69
|
+
WV: 'West Virginia',
|
70
|
+
WI: 'Wisconsin',
|
71
|
+
WY: 'Wyoming'
|
72
|
+
}.freeze
|
73
|
+
|
74
|
+
def trophy_and_medal_colors
|
75
|
+
%w[
|
76
|
+
#ffee58
|
77
|
+
#cfd8dc
|
78
|
+
#d8bf99
|
79
|
+
#ffefc0
|
80
|
+
#dcedc8
|
81
|
+
#f8bbd0
|
82
|
+
#eeccff
|
83
|
+
#fdd5b4
|
84
|
+
#ebedd8
|
85
|
+
#d4f0f1
|
86
|
+
]
|
87
|
+
end
|
88
|
+
|
89
|
+
def trophy_and_medal_css(trophies, medals)
|
90
|
+
trophy_and_medal_colors.map.with_index do |color, i|
|
91
|
+
[
|
92
|
+
("td.event-points[data-points='#{i+1}'] div" if i < medals),
|
93
|
+
("td.event-points-focus[data-points='#{i+1}'] div" if i < medals),
|
94
|
+
("div#team-detail tr[data-points='#{i+1}']" if i < medals),
|
95
|
+
("td.rank[data-points='#{i+1}'] div" if i < trophies)
|
96
|
+
].compact.join(',') + "{background-color: #{color};border-radius: 1em;}"
|
97
|
+
end.join +
|
98
|
+
trophy_and_medal_colors.map.with_index do |color, i|
|
99
|
+
"div#team-detail tr[data-points='#{i+1}'] td:first-child" if i < medals
|
100
|
+
end.compact.join(',') + "{padding-left: 0.5em;}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def tournament_title(t_info)
|
104
|
+
return t_info.name if t_info.name
|
105
|
+
|
106
|
+
case t_info.level
|
107
|
+
when 'Nationals'
|
108
|
+
'Science Olympiad National Tournament'
|
109
|
+
when 'States'
|
110
|
+
"#{expand_state_name(t_info.state)} Science Olympiad State Tournament"
|
111
|
+
when 'Regionals'
|
112
|
+
"#{t_info.location} Regional Tournament"
|
113
|
+
when 'Invitational'
|
114
|
+
"#{t_info.location} Invitational"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def tournament_title_short(t_info)
|
119
|
+
case t_info.level
|
120
|
+
when 'Nationals'
|
121
|
+
'National Tournament'
|
122
|
+
when 'States'
|
123
|
+
"#{t_info.state} State Tournament"
|
124
|
+
when 'Regionals', 'Invitational'
|
125
|
+
t_info.short_name
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def expand_state_name(postal_code)
|
130
|
+
STATES_BY_POSTAL_CODE[postal_code.to_sym]
|
131
|
+
end
|
132
|
+
|
133
|
+
def format_school(team)
|
134
|
+
if team.school_abbreviation
|
135
|
+
abbr_school(team.school_abbreviation)
|
136
|
+
else
|
137
|
+
abbr_school(team.school)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def abbr_school(school)
|
142
|
+
school.sub('Elementary School', 'Elementary')
|
143
|
+
.sub('Elementary/Middle School', 'E.M.S.')
|
144
|
+
.sub('Middle School', 'M.S.')
|
145
|
+
.sub('Junior High School', 'J.H.S.')
|
146
|
+
.sub(%r{Middle[ /-]High School}, 'M.H.S')
|
147
|
+
.sub('Junior/Senior High School', 'Jr./Sr. H.S.')
|
148
|
+
.sub('High School', 'H.S.')
|
149
|
+
.sub('Secondary School', 'Secondary')
|
150
|
+
end
|
151
|
+
|
152
|
+
def full_school_name(team)
|
153
|
+
location = if team.city then "(#{team.city}, #{team.state})"
|
154
|
+
else "(#{team.state})"
|
155
|
+
end
|
156
|
+
[team.school, location].join(' ')
|
157
|
+
end
|
158
|
+
|
159
|
+
def full_team_name(team)
|
160
|
+
location = if team.city then "(#{team.city}, #{team.state})"
|
161
|
+
else "(#{team.state})"
|
162
|
+
end
|
163
|
+
[team.school, team.suffix, location].join(' ')
|
164
|
+
end
|
165
|
+
|
166
|
+
def team_attended?(team)
|
167
|
+
team
|
168
|
+
.placings
|
169
|
+
.map(&:participated?)
|
170
|
+
.any?
|
171
|
+
end
|
172
|
+
|
173
|
+
def summary_titles
|
174
|
+
%w[
|
175
|
+
Champion
|
176
|
+
Runner-up
|
177
|
+
Third-place
|
178
|
+
Fourth-place
|
179
|
+
Fifth-place
|
180
|
+
Sixth-place
|
181
|
+
]
|
182
|
+
end
|
183
|
+
|
184
|
+
def sup_tag(placing)
|
185
|
+
exempt = placing.exempt? || placing.dropped_as_part_of_worst_placings?
|
186
|
+
tie = placing.tie? && !placing.points_limited_by_maximum_place?
|
187
|
+
return '' unless tie || exempt
|
188
|
+
|
189
|
+
"<sup>#{'◊' if exempt}#{'*' if tie}</sup>"
|
190
|
+
end
|
191
|
+
|
192
|
+
def bids_sup_tag(team)
|
193
|
+
return '' unless team.earned_bid?
|
194
|
+
|
195
|
+
"<sup>✧</sup>"
|
196
|
+
end
|
197
|
+
|
198
|
+
def bids_sup_tag_note(tournament)
|
199
|
+
next_tournament = if tournament.level == 'Regionals'
|
200
|
+
"#{tournament.state} State Tournament"
|
201
|
+
else
|
202
|
+
"National Tournament"
|
203
|
+
end
|
204
|
+
qualifiee = tournament.bids_per_school > 1 ? 'team' : 'school'
|
205
|
+
"Qualified #{qualifiee} for the #{tournament.year} #{next_tournament}"
|
206
|
+
end
|
207
|
+
|
208
|
+
def placing_notes(placing)
|
209
|
+
place = placing.place
|
210
|
+
points = placing.isolated_points
|
211
|
+
[
|
212
|
+
('trial event' if placing.event.trial?),
|
213
|
+
('trialed event' if placing.event.trialed?),
|
214
|
+
('disqualified' if placing.disqualified?),
|
215
|
+
('did not participate' if placing.did_not_participate?),
|
216
|
+
('participation points only' if placing.participation_only?),
|
217
|
+
('tie' if placing.tie?),
|
218
|
+
('exempt' if placing.exempt?),
|
219
|
+
('points limited' if placing.points_limited_by_maximum_place?),
|
220
|
+
('unknown place' if placing.unknown?),
|
221
|
+
('placed behind exhibition team'\
|
222
|
+
if placing.points_affected_by_exhibition? && place - points == 1),
|
223
|
+
('placed behind exhibition teams'\
|
224
|
+
if placing.points_affected_by_exhibition? && place - points > 1),
|
225
|
+
('dropped'\
|
226
|
+
if placing.dropped_as_part_of_worst_placings?)
|
227
|
+
].compact.join(', ').capitalize
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|