sciolyff-duosmium 0.13.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/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
|
+
[](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
|