line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Grades; |
2
|
|
|
|
|
|
|
{ |
3
|
|
|
|
|
|
|
$Grades::VERSION = '0.16'; |
4
|
|
|
|
|
|
|
} |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
#Last Edit: 2014 2æ 15, 16æ23å02ç§ |
7
|
|
|
|
|
|
|
#$Id: Grades.pm 1960 2014-02-15 08:27:09Z drbean $ |
8
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
108486
|
use MooseX::Declare; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
package Grades::Script; |
12
|
|
|
|
|
|
|
{ |
13
|
|
|
|
|
|
|
$Grades::Script::VERSION = '0.16'; |
14
|
|
|
|
|
|
|
} |
15
|
|
|
|
|
|
|
use Moose; |
16
|
|
|
|
|
|
|
with 'MooseX::Getopt'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
has 'man' => (is => 'ro', isa => 'Bool'); |
19
|
|
|
|
|
|
|
has 'help' => (is => 'ro', isa => 'Bool'); |
20
|
|
|
|
|
|
|
has 'league' => (metaclass => 'Getopt', is => 'ro', isa => 'Str', |
21
|
|
|
|
|
|
|
cmd_flag => 'l',); |
22
|
|
|
|
|
|
|
has 'exam' => ( metaclass => 'Getopt', is => 'ro', isa => 'Str', |
23
|
|
|
|
|
|
|
cmd_flag => 'e',); |
24
|
|
|
|
|
|
|
has 'session' => ( metaclass => 'Getopt', is => 'ro', isa => 'Str', |
25
|
|
|
|
|
|
|
cmd_flag => 's',); |
26
|
|
|
|
|
|
|
has 'beancan' => ( metaclass => 'Getopt', is => 'ro', isa => 'Int', |
27
|
|
|
|
|
|
|
cmd_flag => 'n',); |
28
|
|
|
|
|
|
|
has 'tables' => ( metaclass => 'Getopt', is => 'ro', isa => 'Str', |
29
|
|
|
|
|
|
|
cmd_flag => 'g',); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
has 'round' => ( metaclass => 'Getopt', is => 'ro', isa => 'Str', |
33
|
|
|
|
|
|
|
cmd_flag => 'r',); |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# letters2score.pl |
36
|
|
|
|
|
|
|
has 'exercise' => ( metaclass => 'Getopt', is => 'ro', isa => 'Str', |
37
|
|
|
|
|
|
|
cmd_flag => 'x',); |
38
|
|
|
|
|
|
|
has 'one' => ( metaclass => 'Getopt', is => 'ro', isa => 'Str', |
39
|
|
|
|
|
|
|
cmd_flag => 'o',); |
40
|
|
|
|
|
|
|
has 'two' => ( metaclass => 'Getopt', is => 'ro', isa => 'Str', |
41
|
|
|
|
|
|
|
cmd_flag => 't',); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
has 'weights' => (metaclass => 'Getopt', is => 'ro', isa => 'Str', |
44
|
|
|
|
|
|
|
cmd_flag => 'w',); |
45
|
|
|
|
|
|
|
has 'player' => (metaclass => 'Getopt', is => 'ro', isa => 'Str', |
46
|
|
|
|
|
|
|
cmd_flag => 'p',); |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
package Grades; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 NAME |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
Grades - A collocation of homework, classwork and exams |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=head1 SYNOPSIS |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
use Grades; |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
my $script = Grades::Script->new_with_options( league => getcwd ); |
59
|
|
|
|
|
|
|
my $league = League->new( id => $script->league ); |
60
|
|
|
|
|
|
|
my $grades = Grades->new( league => $league ); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
$league->approach->meta->apply( $grades ); |
63
|
|
|
|
|
|
|
my $classworkgrades = $grades->classwork; |
64
|
|
|
|
|
|
|
my $homeworkgrades = $grades->homework; |
65
|
|
|
|
|
|
|
my $examgrades = $grades->examGrade; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=head1 DESCRIPTION |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
An alternative to a spreadsheet for grading students, using YAML files and scripts. The students are the players in a league ( class.) See the README and example emile league in t/emile in the distribution for the layout of the league directory in which homework, classwork and exam scores are recorded. |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
Grades are a collocation of Classwork, Homework and Exams roles, but the Classwork role 'delegates' its methods to one of a number of approaches, each of which has a 'total' and 'totalPercent' method. Current approaches, or forms of curriculum, include Compcomp, Groupwork and Jigsaw. |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
Keywords: gold stars, token economies, bean counter |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=cut |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head1 ATTRIBUTES & METHODS |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=cut |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=head2 LEAGUE CLASS |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=cut |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
class League { |
86
|
|
|
|
|
|
|
use YAML qw/LoadFile DumpFile/; |
87
|
|
|
|
|
|
|
use List::MoreUtils qw/any/; |
88
|
|
|
|
|
|
|
use Grades::Types qw/PlayerName PlayerNames Members/; |
89
|
|
|
|
|
|
|
use Try::Tiny; |
90
|
|
|
|
|
|
|
use Carp; |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=head3 leagues |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
The path to the league directory. |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=cut |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
has 'leagues' => (is => 'ro', isa => 'Str', required => 1, lazy => 1, |
99
|
|
|
|
|
|
|
default => '/home/drbean/022' ); |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=head3 id |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Actually, it's a path to the league directory, below the $grades->leagues dir. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=cut |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
has 'id' => (is => 'ro', isa => 'Str', required => 1); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head3 yaml |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
The content of the league configuration file. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=cut |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
has 'yaml' => (is => 'ro', isa => 'HashRef', lazy_build => 1); |
116
|
|
|
|
|
|
|
method _build_yaml { |
117
|
|
|
|
|
|
|
my $leaguedirs = $self->leagues; |
118
|
|
|
|
|
|
|
my $league = $self->id; |
119
|
|
|
|
|
|
|
$self->inspect( "$leaguedirs/$league/league.yaml" ); |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head3 name |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
The name of the league (class). |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=cut |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
has 'name' => (is => 'ro', isa => 'Str', lazy_build => 1); |
129
|
|
|
|
|
|
|
method _build_name { |
130
|
|
|
|
|
|
|
my $data = $self->yaml; |
131
|
|
|
|
|
|
|
$data->{league}; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head3 field |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
The field of the league (class). What is the subject or description, the area of endeavor? |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
has 'field' => (is => 'ro', isa => 'Str', lazy_build => 1); |
142
|
|
|
|
|
|
|
method _build_field { |
143
|
|
|
|
|
|
|
my $data = $self->yaml; |
144
|
|
|
|
|
|
|
$data->{field}; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=head3 approach |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
The style of classwork competition, eg Compcomp, or Groupwork. This is the name of the class (think OOP) to which 'classwork' and other methods are delegated. |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=cut |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
has 'approach' => (is => 'ro', isa => 'Str', lazy => 1, |
155
|
|
|
|
|
|
|
default => sub { shift->yaml->{approach} } ); |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=head3 members |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
Hash refs of the players (students) in the league. The module assumes each of the members in the arrayref returned by this attribute is a hash ref containing an id and name of the member. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=cut |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
has 'members', is => 'ro', isa => Members, lazy_build => 1; |
164
|
|
|
|
|
|
|
method _build_members { |
165
|
|
|
|
|
|
|
my $data = $self->yaml; |
166
|
|
|
|
|
|
|
$data->{member}; |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=head3 session |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
The first week in each session, like { 1 => 1, 2 => 5, 3 => 10, 4 => 14 }, monotonically increasing week numbers. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=cut |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
has 'session', (is => 'ro', isa => 'HashRef', |
176
|
|
|
|
|
|
|
lazy => 1, default => sub { shift->yaml->{session} } ); |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=head3 absentees |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
Students who have stopped coming to class and so won't be included in classwork scoring. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=cut |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
has 'absentees', (is => 'ro', isa => PlayerNames, |
186
|
|
|
|
|
|
|
lazy => 1, default => sub { shift->yaml->{out} } ); |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head3 transfer |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
$oldleague = $newleague->transfer->{V9731059} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
Players who have transferred to this league from some other league at some point and the leagues they transferred from. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=cut |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
has 'transfer', (is => 'ro', isa => 'HashRef', |
198
|
|
|
|
|
|
|
lazy => 1, default => sub { shift->yaml->{transfer} } ); |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=head3 is_member |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
Whether the passed id is that of a member in the league (class). |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=cut |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
method is_member (Str $id) { |
208
|
|
|
|
|
|
|
my $data = $self->yaml; |
209
|
|
|
|
|
|
|
any { $_->{id} eq $id } @{$data->{member}}; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=head3 ided |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
The id of the member with the given player name. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=cut |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
method ided( Str $player) { |
220
|
|
|
|
|
|
|
my $members = $self->members; |
221
|
|
|
|
|
|
|
my %ids = map { $_->{id} => $_->{name} } |
222
|
|
|
|
|
|
|
grep { $_->{name} eq $player } @$members; |
223
|
|
|
|
|
|
|
my @ids = keys %ids; |
224
|
|
|
|
|
|
|
my @names = values %ids; |
225
|
|
|
|
|
|
|
local $" = ', '; |
226
|
|
|
|
|
|
|
carp @ids . " players named @names, with ids: @ids," unless @ids==1; |
227
|
|
|
|
|
|
|
if ( @ids == 1 ) { return $ids[0] } |
228
|
|
|
|
|
|
|
else { return $ids{$player}; } |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=head3 inspect |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Loads a YAML file. |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=cut |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
method inspect (Str $file) { |
238
|
|
|
|
|
|
|
my ($warning, $data); |
239
|
|
|
|
|
|
|
try { $data = LoadFile $file } |
240
|
|
|
|
|
|
|
catch { carp "Couldn't open $file," }; |
241
|
|
|
|
|
|
|
return $data; |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=head3 save |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
Dumps a YAML file |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
=cut |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
method save (Str $file, HashRef $data) { |
251
|
|
|
|
|
|
|
try { DumpFile $file, $data } |
252
|
|
|
|
|
|
|
catch { warn "Couldn't save $data to $file," }; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=head2 PLAYER CLASS |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=cut |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
class Player { |
263
|
|
|
|
|
|
|
use List::MoreUtils qw/firstval/; |
264
|
|
|
|
|
|
|
use List::Util qw/sum/; |
265
|
|
|
|
|
|
|
use POSIX; |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=head3 league |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
The league the player is in. This is required. |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=cut |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
has 'league' => (is => 'ro', isa => 'League', required => 1); |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=head3 id |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
The id of the player. This is required. |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=cut |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
has 'id' => (is => 'ro', isa => 'Str', required => 1); |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=head3 id |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
The name of the player. |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=cut |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
has 'name' => (is => 'ro', isa => 'Str', lazy_build => 1); |
290
|
|
|
|
|
|
|
method _build_name { |
291
|
|
|
|
|
|
|
my $league = $self->league; |
292
|
|
|
|
|
|
|
my $id = $self->id; |
293
|
|
|
|
|
|
|
my $members = $league->members; |
294
|
|
|
|
|
|
|
my $member = firstval { $_->{id} eq $id } @$members; |
295
|
|
|
|
|
|
|
$member->{name}; |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
has 'Chinese' => (is => 'ro', isa => 'Str'); |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
=head2 NONENTITY CLASS |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=cut |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
class Nonentity extends Player { |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=head3 name |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
The name is 'Bye'. The id is too, as a matter of fact. |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
=cut |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
has 'name' => (is => 'ro', isa => 'Str', required => 1 ); |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=head2 GRADES CLASS |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
=head2 Grades' Homework Methods |
322
|
|
|
|
|
|
|
=cut |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
role Homework { |
325
|
|
|
|
|
|
|
use YAML qw/LoadFile DumpFile/; |
326
|
|
|
|
|
|
|
use List::Util qw/min sum/; |
327
|
|
|
|
|
|
|
use Scalar::Util qw/looks_like_number/; |
328
|
|
|
|
|
|
|
use Carp; |
329
|
|
|
|
|
|
|
use Grades::Types qw/PlayerId HomeworkResult HomeworkRound HomeworkRounds |
330
|
|
|
|
|
|
|
RoundsResults/; |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
=head3 hwdir |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
The directory where the homework is. |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=cut |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
has 'hwdir' => (is => 'ro', isa => 'Str', lazy_build => 1); |
339
|
|
|
|
|
|
|
method _build_hwdir { |
340
|
|
|
|
|
|
|
my $league = $self->league->id; |
341
|
|
|
|
|
|
|
my $leaguedir = $self->league->leagues . "/" . $league; |
342
|
|
|
|
|
|
|
my $basename = shift->league->yaml->{hw} || "exams"; |
343
|
|
|
|
|
|
|
my $hwdir = $leaguedir . '/' . $basename; |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
=head3 rounds |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
An arrayref of the rounds for which there are homework grades for players in the league, in round order, of the form, [1, 3 .. 7, 9 ..]. |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
=cut |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
has 'rounds', (is => 'ro', isa => 'ArrayRef[Int]', lazy_build => 1); |
353
|
|
|
|
|
|
|
method _build_rounds { |
354
|
|
|
|
|
|
|
my $hwdir = $self->hwdir; |
355
|
|
|
|
|
|
|
my @hw = glob "$hwdir/*.yaml"; |
356
|
|
|
|
|
|
|
[ sort {$a<=>$b} map m/^$hwdir\/(\d+)\.yaml$/, @hw ]; |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
=head3 roundIndex |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
Given a round name (ie number), returns the ordinal position in which this round was played, with the first round numbered 0. Returns undef if the round was not played. |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
=cut |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
method roundIndex (Int $round) { |
366
|
|
|
|
|
|
|
my $rounds = $self->rounds; |
367
|
|
|
|
|
|
|
my $n = 0; |
368
|
|
|
|
|
|
|
for ( @$rounds ) { |
369
|
|
|
|
|
|
|
return $n if $_ eq $round; |
370
|
|
|
|
|
|
|
$n++; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=head3 roundfiles |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
An hashref of the files with data for the rounds for which there are homework grades for players in the league, keyed on rounds. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=cut |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
has 'roundfiles', (is => 'ro', isa => 'HashRef[ArrayRef]', lazy_build => 1); |
381
|
|
|
|
|
|
|
method _build_roundfiles { |
382
|
|
|
|
|
|
|
my $hwdir = $self->hwdir; |
383
|
|
|
|
|
|
|
my @hw = glob "$hwdir/*.yaml"; |
384
|
|
|
|
|
|
|
my @rounds = map m/^$hwdir\/(\d+)\.yaml$/, @hw; |
385
|
|
|
|
|
|
|
+{ map { $_ => [ glob "$hwdir/${_}*.yaml" ] } @rounds } |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
=head3 hwbyround |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
A hashref of the homework grades for players in the league for each round. |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=cut |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
has 'hwbyround', (is => 'ro', isa => RoundsResults, lazy_build => 1); |
395
|
|
|
|
|
|
|
method _build_hwbyround { |
396
|
|
|
|
|
|
|
my $hwdir = $self->hwdir; |
397
|
|
|
|
|
|
|
my $rounds = $self->rounds; |
398
|
|
|
|
|
|
|
my %results = |
399
|
|
|
|
|
|
|
map { $_ => $self->inspect("$hwdir/$_.yaml") } @$rounds; |
400
|
|
|
|
|
|
|
my %grades = map { $_ => $results{$_}{grade} } @$rounds; |
401
|
|
|
|
|
|
|
return \%grades; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
=head3 hwMax |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
The highest possible score in the homework |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=cut |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
has 'hwMax' => (is => 'ro', isa => 'Int', lazy => 1, default => |
411
|
|
|
|
|
|
|
sub { shift->league->yaml->{hwMax} } ); |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=head3 totalMax |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
The total maximum points that a Player could have gotten to this point in the whole season. There may be more (or fewer) rounds played than expected, so the actual top possible score returned by totalMax may be more (or less) than the figure planned. |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=cut |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
has 'totalMax' => (is => 'ro', isa => 'Int', lazy_build => 1); |
420
|
|
|
|
|
|
|
method _build_totalMax { |
421
|
|
|
|
|
|
|
my $rounds = $self->rounds; |
422
|
|
|
|
|
|
|
my $hwMax = $self->hwMax; |
423
|
|
|
|
|
|
|
$hwMax * @$rounds; |
424
|
|
|
|
|
|
|
} |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=head3 rawscoresinRound |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
Given a round, returns a hashref of the raw scores for that round, keyed on the names of the exercises. These are in files in the hwdir with names of the form ^\d+[_.]\w+\.yaml$ |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
=cut |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
method rawscoresinRound (Int $round) { |
433
|
|
|
|
|
|
|
my $hwdir = $self->hwdir; |
434
|
|
|
|
|
|
|
my $files = $self->roundfiles->{$round}; |
435
|
|
|
|
|
|
|
my @ex = map m/^$hwdir\/$round([_.]\w+)\.yaml$/, @$files; |
436
|
|
|
|
|
|
|
my $results = $self->inspect("$hwdir/$round.yaml"); |
437
|
|
|
|
|
|
|
return { $results->{exercise} => $results->{points} }; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=head3 hwforid |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
Given a player's id, returns an array ref of the player's hw scores. |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=cut |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
method hwforid( PlayerId $id) { |
447
|
|
|
|
|
|
|
my $leagueId = $self->league->id; |
448
|
|
|
|
|
|
|
my $hw = $self->hwbyround; |
449
|
|
|
|
|
|
|
my $rounds = $self->rounds; |
450
|
|
|
|
|
|
|
my @hwbyid; |
451
|
|
|
|
|
|
|
for my $round (@$rounds) { |
452
|
|
|
|
|
|
|
unless ( $hw->{$round} ) { |
453
|
|
|
|
|
|
|
warn "No homework results in Round $round in $leagueId league"; |
454
|
|
|
|
|
|
|
next; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
my $grade = $hw->{$round}->{$id}; |
457
|
|
|
|
|
|
|
if ( defined $grade and looks_like_number( $grade ) ) { |
458
|
|
|
|
|
|
|
push @hwbyid, $grade; |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
elsif ( defined $grade and $grade =~ m/transfer/i ) { |
461
|
|
|
|
|
|
|
my $oldleagueId = $self->league->transfer->{$id}; |
462
|
|
|
|
|
|
|
my $league = League->new( id => $oldleagueId ); |
463
|
|
|
|
|
|
|
my $grades = Grades->new({ league => $league }); |
464
|
|
|
|
|
|
|
my $transfergrade = $grades->hwbyround->{$round}->{$id}; |
465
|
|
|
|
|
|
|
warn |
466
|
|
|
|
|
|
|
"$id transfered from $oldleagueId league but no homework there in round $round" |
467
|
|
|
|
|
|
|
unless defined $transfergrade; |
468
|
|
|
|
|
|
|
push @hwbyid, $transfergrade || 0; |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
else { |
471
|
|
|
|
|
|
|
warn "No homework result for $id in Round $round in $leagueId league\n"; |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
} |
474
|
|
|
|
|
|
|
\@hwbyid; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=head3 hwforidasHash |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
Given a player's id, returns an hashref of the player's hw grades, keyed on the rounds. |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
=cut |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
method hwforidasHash (PlayerId $id) { |
484
|
|
|
|
|
|
|
my $hw = $self->hwforid( $id ); |
485
|
|
|
|
|
|
|
my $rounds = $self->rounds; |
486
|
|
|
|
|
|
|
my %hwbyid; |
487
|
|
|
|
|
|
|
for my $i ( 0 .. $#$rounds ) { |
488
|
|
|
|
|
|
|
my $round = $rounds->[$i]; |
489
|
|
|
|
|
|
|
$hwbyid{$round} = $hw->[$i]; |
490
|
|
|
|
|
|
|
if ( not defined $hw->[$i] ) { warn |
491
|
|
|
|
|
|
|
"No homework result for $id in Round $round\n";} |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
\%hwbyid; |
494
|
|
|
|
|
|
|
} |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=head3 homework |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
Running total homework scores of the league. |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=cut |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
method homework { |
503
|
|
|
|
|
|
|
my $league = $self->league; |
504
|
|
|
|
|
|
|
my $leagueId = $league->id; |
505
|
|
|
|
|
|
|
my $players = $league->members; |
506
|
|
|
|
|
|
|
my %players = map { $_->{id} => $_ } @$players; |
507
|
|
|
|
|
|
|
my %idtotals; |
508
|
|
|
|
|
|
|
for my $player ( keys %players ) { |
509
|
|
|
|
|
|
|
my $homework = $self->hwforid( $player ); |
510
|
|
|
|
|
|
|
my $total = sum @$homework; |
511
|
|
|
|
|
|
|
$idtotals{$player} = $total; |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
+{ map { $_ => $idtotals{$_} || 0 } keys %idtotals }; |
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=head3 homeworkPercent |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
Running total homework scores of the league as percentages of the totalMax to that point, with a maximum of 100. |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=cut |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
method homeworkPercent { |
523
|
|
|
|
|
|
|
my $league = $self->league->id; |
524
|
|
|
|
|
|
|
my $totalMax = $self->totalMax; |
525
|
|
|
|
|
|
|
my $idtotals = $self->homework; |
526
|
|
|
|
|
|
|
my %percent; |
527
|
|
|
|
|
|
|
if ( $totalMax == 0 ) { |
528
|
|
|
|
|
|
|
$percent{$_} = 0 for keys %$idtotals; |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
else { |
531
|
|
|
|
|
|
|
%percent = map { |
532
|
|
|
|
|
|
|
$_ => min( 100, 100 * $idtotals->{$_} / $totalMax ) |
533
|
|
|
|
|
|
|
|| 0 } keys %$idtotals; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
return \%percent; |
536
|
|
|
|
|
|
|
} |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=head2 Grades' Jigsaw Methods |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
The jigsaw is a cooperative learning activity where all the players in a group get different information that together produces the 'big picture', and where they are each held responsible for the understanding of each of the other individual members of this big picture. |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
=cut |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
role Jigsaw { |
547
|
|
|
|
|
|
|
use List::MoreUtils qw/any all/; |
548
|
|
|
|
|
|
|
use Try::Tiny; |
549
|
|
|
|
|
|
|
use Moose::Autobox; |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=head3 jigsawdirs |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
The directory where the jigsaws are. |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
=cut |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
has 'jigsawdirs' => (is => 'ro', isa => 'Str', lazy_build => 1); |
558
|
|
|
|
|
|
|
method _build_jigsawdirs { |
559
|
|
|
|
|
|
|
my $league = $self->league->id; |
560
|
|
|
|
|
|
|
my $leaguedir = $self->league->leagues . "/" . $league; |
561
|
|
|
|
|
|
|
my $basename = shift->league->yaml->{jigsaw} || "exam"; |
562
|
|
|
|
|
|
|
my $jigsawdir = $leaguedir .'/' . $basename; |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
=head3 config |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
The round.yaml file with data about the jigsaw activity in the given round (directory.) |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=cut |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
method config( Str $round) { |
572
|
|
|
|
|
|
|
my $jigsaws = $self->jigsawdirs; |
573
|
|
|
|
|
|
|
my $config; |
574
|
|
|
|
|
|
|
try { $config = $self->inspect("$jigsaws/$round/round.yaml") } |
575
|
|
|
|
|
|
|
catch { warn "No config file for $jigsaws/$round jigsaw" }; |
576
|
|
|
|
|
|
|
return $config; |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=head3 topic |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
The topic of the quiz in the given jigsaw for the given group. |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
=cut |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
method topic ( Str $jigsaw, Str $group ) { |
586
|
|
|
|
|
|
|
my $config = $self->config('Jigsaw', $jigsaw); |
587
|
|
|
|
|
|
|
my $activity = $config->{activity}; |
588
|
|
|
|
|
|
|
for my $topic ( keys %$activity ) { |
589
|
|
|
|
|
|
|
my $forms = $activity->{$topic}; |
590
|
|
|
|
|
|
|
for my $form ( keys %$forms ) { |
591
|
|
|
|
|
|
|
my $tables = $forms->{$form}; |
592
|
|
|
|
|
|
|
return $topic if any { $_ eq $group } @$tables; |
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
return; |
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=head3 form |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
The form of the quiz in the given jigsaw for the given group. |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=cut |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
method form ( Str $jigsaw, Str $group ) { |
605
|
|
|
|
|
|
|
my $config = $self->config('Jigsaw', $jigsaw); |
606
|
|
|
|
|
|
|
my $activity = $config->{activity}; |
607
|
|
|
|
|
|
|
for my $topic ( keys %$activity ) { |
608
|
|
|
|
|
|
|
my $forms = $activity->{$topic}; |
609
|
|
|
|
|
|
|
for my $form ( keys %$forms ) { |
610
|
|
|
|
|
|
|
my $tables = $forms->{$form}; |
611
|
|
|
|
|
|
|
return $form if any { $_ eq $group } @$tables; |
612
|
|
|
|
|
|
|
} |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
return; |
615
|
|
|
|
|
|
|
} |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=head3 quizfile |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
The file system location of the file with the quiz questions and answers for the given jigsaw. |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
=cut |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
method quizfile ( Str $jigsaw ) { |
624
|
|
|
|
|
|
|
my $config = $self->config('Jigsaw', $jigsaw); |
625
|
|
|
|
|
|
|
return $config->{text}; |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=head3 quiz |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
The quiz questions (as an anon array) in the given jigsaw for the given group. |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=cut |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
method quiz ( Str $jigsaw, Str $group ) { |
635
|
|
|
|
|
|
|
my $quizfile = $self->quizfile($jigsaw); |
636
|
|
|
|
|
|
|
my $activity; |
637
|
|
|
|
|
|
|
try { $activity = $self->inspect( $quizfile ) } |
638
|
|
|
|
|
|
|
catch { warn "No $quizfile jigsaw content file" }; |
639
|
|
|
|
|
|
|
my $topic = $self->topic( $jigsaw, $group ); |
640
|
|
|
|
|
|
|
my $form = $self->form( $jigsaw, $group ); |
641
|
|
|
|
|
|
|
my $quiz = $activity->{$topic}->{jigsaw}->{$form}->{quiz}; |
642
|
|
|
|
|
|
|
} |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=head3 options |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
$grades->options( '2/1', 'Purple', 0 ) # [ qw/Deborah Don Dovonna Sue/ ] |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
The options (as an anon array) to the given question in the given jigsaw for the given group. |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=cut |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
method options ( Str $jigsaw, Str $group, Int $question ) { |
653
|
|
|
|
|
|
|
my $quiz = $self->quiz( $jigsaw, $group ); |
654
|
|
|
|
|
|
|
my $options = $quiz->[$question]->{option}; |
655
|
|
|
|
|
|
|
return $options || ''; |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
=head3 qn |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
The number of questions in the given jigsaw for the given group. |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
=cut |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
method qn ( Str $jigsaw, Str $group ) { |
665
|
|
|
|
|
|
|
my $quiz = $self->quiz( $jigsaw, $group ); |
666
|
|
|
|
|
|
|
warn "No quiz for $group group in jigsaw $jigsaw," unless $quiz; |
667
|
|
|
|
|
|
|
return scalar @$quiz; |
668
|
|
|
|
|
|
|
} |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
=head3 responses |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
The responses of the members of the given group in the given jigsaw (as an anon hash keyed on the ids of the members). In a file in the jigsaw directory called 'response.yaml'. |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
=cut |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
method responses ( Str $jigsaw, Str $group ) { |
678
|
|
|
|
|
|
|
my $jigsaws = $self->jigsawdirs; |
679
|
|
|
|
|
|
|
my $responses = $self->inspect( "$jigsaws/$jigsaw/response.yaml" ); |
680
|
|
|
|
|
|
|
return $responses->{$group}; |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
=head3 jigsawGroups |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
A hash ref of all the groups in the given jigsaw and the names of members of the groups, keyed on groupnames. There may be duplicated names if one player did the activity twice as an 'assistant' for a group with not enough players, and missing names if a player did not do the quiz. |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
=cut |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
method jigsawGroups (Str $jigsaw ) { |
690
|
|
|
|
|
|
|
my $config = $self->config('Jigsaw', $jigsaw ); |
691
|
|
|
|
|
|
|
$config->{group}; |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
=head3 jigsawGroupMembers |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
An array (was hash ref) of the names of the members of the given group in the given jigsaw, in order of the roles, A..D. |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
=cut |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
method jigsawGroupMembers (Str $jigsaw, Str $group) { |
701
|
|
|
|
|
|
|
my $groups = $self->jigsawGroups( $jigsaw ); |
702
|
|
|
|
|
|
|
my $members = $groups->{$group}; |
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
=head3 roles |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
At the moment, just A .. D. |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
=cut |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
has 'roles' => (is => 'ro', isa => 'ArrayRef[Str]', |
712
|
|
|
|
|
|
|
default => sub { [ qw/A B C D/ ] } ); |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
=head3 idsbyRole |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
Ids in array, in A-D role order |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
=cut |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
method idsbyRole ( Str $jigsaw, Str $group ) { |
723
|
|
|
|
|
|
|
my $members = $self->league->members; |
724
|
|
|
|
|
|
|
my %namedMembers = map { $_->{name} => $_ } @$members; |
725
|
|
|
|
|
|
|
my $namesbyRole = $self->jigsawGroupMembers( $jigsaw, $group ); |
726
|
|
|
|
|
|
|
my @idsbyRole = map { $namedMembers{$_}->{id} } @$namesbyRole; |
727
|
|
|
|
|
|
|
return \@idsbyRole; |
728
|
|
|
|
|
|
|
} |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
=head3 assistants |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
A array ref of all the players in the (sub)jigsaw who did the the activity twice to 'assist' groups with not enough (or absent) players, or individuals with no groups, or people who arrived late. |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
=cut |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
method assistants (Str $jigsaw) { |
737
|
|
|
|
|
|
|
my $round = $self->config( $jigsaw ); |
738
|
|
|
|
|
|
|
$round->{assistants}; |
739
|
|
|
|
|
|
|
} |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
=head3 jigsawGroupRole |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
An hash ref of the roles of the members of the given group in the given jigsaw, keyed on the name of the player. |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
=cut |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
method jigsawGroupRole (Str $jigsaw, Str $group) { |
748
|
|
|
|
|
|
|
my $members = $self->jigsawGroupMembers( $jigsaw, $group ); |
749
|
|
|
|
|
|
|
my %roles; |
750
|
|
|
|
|
|
|
@roles{ @$members } = $self->roles->flatten; |
751
|
|
|
|
|
|
|
return \%roles; |
752
|
|
|
|
|
|
|
} |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=head3 id2jigsawGroupRole |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
An hash ref of the roles of the members of the given group in the given jigsaw, keyed on the id of the player. |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
=cut |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
method id2jigsawGroupRole (Str $jigsaw, Str $group) { |
761
|
|
|
|
|
|
|
my $members = $self->jigsawGroupMembers( $jigsaw, $group ); |
762
|
|
|
|
|
|
|
my @ids = map { $self->league->ided($_) } @$members; |
763
|
|
|
|
|
|
|
my $roles = $self->roles; |
764
|
|
|
|
|
|
|
my %id2role; @id2role{@ids} = @$roles; |
765
|
|
|
|
|
|
|
return \%id2role; |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
=head3 name2jigsawGroup |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
An array ref of the group(s) to which the given name belonged in the given jigsaw. Normally, the array ref has only one element. But if the player was an assistant an array ref of more than one group is returned. If the player did not do the jigsaw, no groups are returned. |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
=cut |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
method name2jigsawGroup (Str $jigsaw, Str $name) { |
775
|
|
|
|
|
|
|
my $groups = $self->jigsawGroups( $jigsaw ); |
776
|
|
|
|
|
|
|
my @memberships; |
777
|
|
|
|
|
|
|
for my $id ( keys %$groups ) { |
778
|
|
|
|
|
|
|
my $group = $groups->{$id}; |
779
|
|
|
|
|
|
|
push @memberships, $id if any { $_ eq $name } @$group; |
780
|
|
|
|
|
|
|
} |
781
|
|
|
|
|
|
|
return \@memberships; |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
=head3 rawJigsawScores |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
The individual scores on the given quiz of each member of the given group, keyed on their roles, no, ids, from the file called 'scores.yaml' in the given jigsaw dir. If the scores in that file have a key which is a role, handle that, but, yes, the keys of the hashref returned here are the players' ids. |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=cut |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
method rawJigsawScores (Str $round, Str $group) { |
791
|
|
|
|
|
|
|
my $data; |
792
|
|
|
|
|
|
|
my $jigsaws = $self->jigsawdirs; |
793
|
|
|
|
|
|
|
try { $data = $self->inspect( "$jigsaws/$round/scores.yaml"); } |
794
|
|
|
|
|
|
|
catch { warn "No scores for $group group in jigsaw $round."; }; |
795
|
|
|
|
|
|
|
my $groupdata = $data->{letters}->{$group}; |
796
|
|
|
|
|
|
|
my $ids = $self->idsbyRole( $round, $group ); |
797
|
|
|
|
|
|
|
my $roles = $self->roles; |
798
|
|
|
|
|
|
|
my @keys; |
799
|
|
|
|
|
|
|
if ( |
800
|
|
|
|
|
|
|
any { my $key = $_; any { $_ eq $key } @$roles; } keys %$groupdata |
801
|
|
|
|
|
|
|
) { |
802
|
|
|
|
|
|
|
@keys = @$roles; |
803
|
|
|
|
|
|
|
} |
804
|
|
|
|
|
|
|
else { |
805
|
|
|
|
|
|
|
@keys = grep { my $id = $_; any { $_ eq $id } @$ids } |
806
|
|
|
|
|
|
|
keys %$groupdata; |
807
|
|
|
|
|
|
|
} |
808
|
|
|
|
|
|
|
my %scores; |
809
|
|
|
|
|
|
|
@scores{@keys} = @{$groupdata}{@keys}; |
810
|
|
|
|
|
|
|
return \%scores; |
811
|
|
|
|
|
|
|
} |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
=head3 chinese |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
The number of times Chinese was used in the given round by all the groups. If there is no record of Chinese use, returns values of 0. |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
=cut |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
method chinese (Str $round) { |
820
|
|
|
|
|
|
|
my $data; |
821
|
|
|
|
|
|
|
my $jigsaws = $self->jigsawdirs; |
822
|
|
|
|
|
|
|
try { $data = $self->inspect( "$jigsaws/$round/scores.yaml"); } |
823
|
|
|
|
|
|
|
catch { warn "No scores in jigsaw $round."; }; |
824
|
|
|
|
|
|
|
my $chinese = $data->{Chinese}; |
825
|
|
|
|
|
|
|
my $groups = $self->jigsawGroups( $round ); |
826
|
|
|
|
|
|
|
$chinese->{ $_ } ||= 0 for keys %$groups; |
827
|
|
|
|
|
|
|
return $chinese; |
828
|
|
|
|
|
|
|
} |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
=head3 jigsawDeduction |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
Points deducted for undesirable performance elements (ie Chinese use) on the quiz of the given group in the given exam. |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
=cut |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
method jigsawDeduction (Str $jigsaw, Str $group) { |
837
|
|
|
|
|
|
|
my $data; |
838
|
|
|
|
|
|
|
my $jigsaws = $self->jigsawdirs; |
839
|
|
|
|
|
|
|
try { $data = $self->inspect( "$jigsaws/$jigsaw/scores.yaml" ); } |
840
|
|
|
|
|
|
|
catch { warn |
841
|
|
|
|
|
|
|
"Deductions for $group group in $jigsaw jigsaw?" }; |
842
|
|
|
|
|
|
|
my $demerits = $data->{Chinese}->{$group}; |
843
|
|
|
|
|
|
|
return $demerits; |
844
|
|
|
|
|
|
|
} |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
} |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
=head2 Grades' Classwork Methods |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
Classwork is work done in class with everyone and the teacher present. Two classwork approaches are Compcomp and Groupwork. Others are possible. Depending on the league's approach accessor, the methods are delegated to the appropriate Approach object. |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
=cut |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
class Classwork { |
856
|
|
|
|
|
|
|
use Grades::Types qw/Results/; |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
=head3 approach |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
Delegatee handling classwork_total, classworkPercent |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
=cut |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
has 'approach' => ( is => 'ro', isa => 'Approach', required => 1, |
865
|
|
|
|
|
|
|
handles => [ qw/ |
866
|
|
|
|
|
|
|
series beancans |
867
|
|
|
|
|
|
|
all_events points |
868
|
|
|
|
|
|
|
classwork_total classworkPercent / ] ); |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
} |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
=head2 Classwork Approach |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
Handles Classwork's classwork_total and classworkPercent methods. Calls the total or totalPercent methods of the class whose name is in the 'type' accessor. |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
=cut |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
class Approach { |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
=head3 league |
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
The league (object) whose approach this is. |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
=cut |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
has 'league' => (is =>'ro', isa => 'League', required => 1, |
887
|
|
|
|
|
|
|
handles => [ 'inspect' ] ); |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
=head3 groupworkdirs |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
The directory under which there are subdirectories containing data for the group/pair-work sessions. Look first in 'groupwork', then 'compcomp' mappings, else use 'classwork' dir. |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
=cut |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
has 'groupworkdirs' => (is => 'ro', isa => 'Str', lazy_build => 1); |
896
|
|
|
|
|
|
|
method _build_groupworkdirs { |
897
|
|
|
|
|
|
|
my $league = $self->league; |
898
|
|
|
|
|
|
|
my $id = $league->id; |
899
|
|
|
|
|
|
|
my $leaguedir = $self->league->leagues . "/" . $id; |
900
|
|
|
|
|
|
|
my $basename = $league->yaml->{groupwork} || |
901
|
|
|
|
|
|
|
$league->yaml->{compcomp} || "classwork"; |
902
|
|
|
|
|
|
|
my $groupworkdirs = $leaguedir .'/' . $basename; |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
=head3 series |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
The sessions (weeks) over the series (semester) in each of which there was a different grouping and results of players. This method returns an arrayref of the names (numbers) of the sessions, in numerical order, of the form, [1, 3 .. 7, 9, 10 .. 99 ]. Results are in sub directories of the same name, under groupworkdirs. |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
=cut |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
has 'series' => |
912
|
|
|
|
|
|
|
( is => 'ro', isa => 'Maybe[ArrayRef[Int]]', lazy_build => 1 ); |
913
|
|
|
|
|
|
|
method _build_series { |
914
|
|
|
|
|
|
|
my $dir = $self->groupworkdirs; |
915
|
|
|
|
|
|
|
my @subdirs = grep { -d } glob "$dir/*"; |
916
|
|
|
|
|
|
|
[ sort { $a <=> $b } map m/^$dir\/(\d+)$/, @subdirs ]; |
917
|
|
|
|
|
|
|
} |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
#=head3 all_events |
920
|
|
|
|
|
|
|
# |
921
|
|
|
|
|
|
|
#All the weeks, or sessions or lessons for which grade data is being assembled from for the grade component. |
922
|
|
|
|
|
|
|
# |
923
|
|
|
|
|
|
|
#=cut |
924
|
|
|
|
|
|
|
# |
925
|
|
|
|
|
|
|
# method all_events { |
926
|
|
|
|
|
|
|
# my $league = $self->league; |
927
|
|
|
|
|
|
|
# my $type = $league->approach; |
928
|
|
|
|
|
|
|
# my $meta = $type->meta; |
929
|
|
|
|
|
|
|
# my $total = $type->new( league => $league )->all_events; |
930
|
|
|
|
|
|
|
# } |
931
|
|
|
|
|
|
|
# |
932
|
|
|
|
|
|
|
#=head3 points |
933
|
|
|
|
|
|
|
# |
934
|
|
|
|
|
|
|
#Week-by-weeks, or session scores for the individual players in the league. |
935
|
|
|
|
|
|
|
# |
936
|
|
|
|
|
|
|
#=cut |
937
|
|
|
|
|
|
|
# |
938
|
|
|
|
|
|
|
# method points (Str $week) { |
939
|
|
|
|
|
|
|
# my $league = $self->league; |
940
|
|
|
|
|
|
|
# my $type = $league->approach; |
941
|
|
|
|
|
|
|
# my $meta = $type->meta; |
942
|
|
|
|
|
|
|
# my $total = $type->new( league => $league )->points( $week ); |
943
|
|
|
|
|
|
|
# } |
944
|
|
|
|
|
|
|
# |
945
|
|
|
|
|
|
|
#=head3 classwork_total |
946
|
|
|
|
|
|
|
# |
947
|
|
|
|
|
|
|
#Calls the pluginned approach's classwork_total. |
948
|
|
|
|
|
|
|
# |
949
|
|
|
|
|
|
|
#=cut |
950
|
|
|
|
|
|
|
# |
951
|
|
|
|
|
|
|
# method classwork_total { |
952
|
|
|
|
|
|
|
# my $league = $self->league; |
953
|
|
|
|
|
|
|
# my $type = $league->approach; |
954
|
|
|
|
|
|
|
# my $total = $type->new( league => $league )->total; |
955
|
|
|
|
|
|
|
# } |
956
|
|
|
|
|
|
|
# |
957
|
|
|
|
|
|
|
=head3 classworkPercent |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
Calls the pluginned approach's classworkPercent. |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
=cut |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
method classworkPercent { |
964
|
|
|
|
|
|
|
my $league = $self->league; |
965
|
|
|
|
|
|
|
my $type = $league->approach; |
966
|
|
|
|
|
|
|
my $total = $type->new( league => $league )->totalPercent; |
967
|
|
|
|
|
|
|
} |
968
|
|
|
|
|
|
|
} |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
=head2 Grades' Compcomp Methods |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
The comprehension question competition is a Swiss tournament regulated 2-partner conversation competition where players try to understand more of their opponent's information than their partners understand of theirs. |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
=cut |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
class Compcomp extends Approach { |
978
|
|
|
|
|
|
|
use Try::Tiny; |
979
|
|
|
|
|
|
|
use Moose::Autobox; |
980
|
|
|
|
|
|
|
use List::Util qw/max min/; |
981
|
|
|
|
|
|
|
use List::MoreUtils qw/any all/; |
982
|
|
|
|
|
|
|
use Carp qw/carp/; |
983
|
|
|
|
|
|
|
use Grades::Types qw/Results/; |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
=head3 compcompdirs |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
The directory under which there are subdirectories containing data for the Compcomp rounds. |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
=cut |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
has 'compcompdirs' => (is => 'ro', isa => 'Str', lazy_build => 1 ); |
992
|
|
|
|
|
|
|
method _build_compcompdirs { |
993
|
|
|
|
|
|
|
my $leaguedir = $self->league->leagues . "/" . $self->league->id; |
994
|
|
|
|
|
|
|
my $compcompdir = $leaguedir .'/' . shift->league->yaml->{compcomp}; |
995
|
|
|
|
|
|
|
} |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
=head3 all_events |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
The pair conversations over the series (semester). This method returns an arrayref of the numbers of the conversations, in numerical order, of the form, [1, 3 .. 7, 9, 10 .. 99 ]. Results are in sub directories of the same name, under compcompdirs. |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
=cut |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
has 'all_events' => |
1004
|
|
|
|
|
|
|
( is => 'ro', isa => 'Maybe[ArrayRef[Int]]', lazy_build => 1 ); |
1005
|
|
|
|
|
|
|
method _build_all_events { |
1006
|
|
|
|
|
|
|
my $dir = $self->compcompdirs; |
1007
|
|
|
|
|
|
|
my @subdirs = grep { -d } glob "$dir/*"; |
1008
|
|
|
|
|
|
|
[ sort { $a <=> $b } map m/^$dir\/(\d+)$/, @subdirs ]; |
1009
|
|
|
|
|
|
|
} |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
=head3 config |
1012
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
The round.yaml file with data about the Compcomp activity for the given conversation (directory.) |
1014
|
|
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
=cut |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
method config( Str $round) { |
1018
|
|
|
|
|
|
|
my $comp = $self->compcompdirs; |
1019
|
|
|
|
|
|
|
my $file = "$comp/$round/round.yaml"; |
1020
|
|
|
|
|
|
|
my $config; |
1021
|
|
|
|
|
|
|
try { $config = $self->inspect($file) } |
1022
|
|
|
|
|
|
|
catch { warn "No config file for Compcomp round $round at $file" }; |
1023
|
|
|
|
|
|
|
return $config; |
1024
|
|
|
|
|
|
|
} |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
=head3 activities |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
The activities which individual tables did in the given round. Keys are topics, keyed are forms. These, in turn, are keys of tables doing those topics and those forms. |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=cut |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
method activities( Str $round ) { |
1033
|
|
|
|
|
|
|
my $config = $self->config( $round ); |
1034
|
|
|
|
|
|
|
return $config->{activity}; |
1035
|
|
|
|
|
|
|
} |
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
=head3 tables |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
The tables with players according to their roles for the given round, as an hash ref. In the 'group' or 'activities' mapping in the config file. Make sure each table has a unique table number. Some code here is same as in Swiss's round_table.pl and dblineup.rc. |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
activities: |
1042
|
|
|
|
|
|
|
drbean: |
1043
|
|
|
|
|
|
|
1: |
1044
|
|
|
|
|
|
|
- U9931007 |
1045
|
|
|
|
|
|
|
- U9933022 |
1046
|
|
|
|
|
|
|
novak: |
1047
|
|
|
|
|
|
|
1: |
1048
|
|
|
|
|
|
|
- U9931028 |
1049
|
|
|
|
|
|
|
- U9933045 |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
=cut |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
method tables ( Str $round ) { |
1054
|
|
|
|
|
|
|
my $config = $self->config($round); |
1055
|
|
|
|
|
|
|
my (@pairs, %pairs, @dupes, $wantlist); |
1056
|
|
|
|
|
|
|
my $groups = $config->{group}; |
1057
|
|
|
|
|
|
|
return $groups if $groups; |
1058
|
|
|
|
|
|
|
my $activities = $config->{activity}; |
1059
|
|
|
|
|
|
|
for my $key ( keys %$activities ) { |
1060
|
|
|
|
|
|
|
my $topic = $activities->{$key}; |
1061
|
|
|
|
|
|
|
for my $form ( keys %$topic ) { |
1062
|
|
|
|
|
|
|
my $pairs = $topic->{$form}; |
1063
|
|
|
|
|
|
|
if ( ref( $pairs ) eq 'ARRAY' ) { |
1064
|
|
|
|
|
|
|
$wantlist = 1; |
1065
|
|
|
|
|
|
|
for my $pair ( @$pairs ) { |
1066
|
|
|
|
|
|
|
my @players = values %$pair; |
1067
|
|
|
|
|
|
|
my @roles = keys %$pair; |
1068
|
|
|
|
|
|
|
push @pairs, $pair unless |
1069
|
|
|
|
|
|
|
any { my @previous = values %$_; |
1070
|
|
|
|
|
|
|
any { my $player=$_; |
1071
|
|
|
|
|
|
|
any { $player eq $_ } @previous |
1072
|
|
|
|
|
|
|
} @players |
1073
|
|
|
|
|
|
|
} @pairs; |
1074
|
|
|
|
|
|
|
} |
1075
|
|
|
|
|
|
|
} |
1076
|
|
|
|
|
|
|
else { |
1077
|
|
|
|
|
|
|
for my $n ( keys %$pairs ) { |
1078
|
|
|
|
|
|
|
my $pair = $pairs->{$n}; |
1079
|
|
|
|
|
|
|
my @twoplayers = values %$pair; |
1080
|
|
|
|
|
|
|
die "Table number $n with players @twoplayers is dupe" if |
1081
|
|
|
|
|
|
|
exists $pairs{$n} or |
1082
|
|
|
|
|
|
|
any { my $player = $_; any { $player eq $_ } @dupes |
1083
|
|
|
|
|
|
|
} @twoplayers; |
1084
|
|
|
|
|
|
|
push @dupes, @twoplayers; |
1085
|
|
|
|
|
|
|
$pairs{ $n } = $pair; |
1086
|
|
|
|
|
|
|
} |
1087
|
|
|
|
|
|
|
} |
1088
|
|
|
|
|
|
|
} |
1089
|
|
|
|
|
|
|
} |
1090
|
|
|
|
|
|
|
return \@pairs if $wantlist; |
1091
|
|
|
|
|
|
|
return \%pairs; |
1092
|
|
|
|
|
|
|
} |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
=head3 pair2table |
1095
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
A player and opponent mapped to a table number. |
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
=cut |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
method pair2table ( Str $player, Str $opponent, Str $round ) { |
1101
|
|
|
|
|
|
|
my $table = $self->tables( $round ); |
1102
|
|
|
|
|
|
|
for my $n ( keys %$table ) { |
1103
|
|
|
|
|
|
|
my $table = $table->{$n}; |
1104
|
|
|
|
|
|
|
my @pair = values %$table; |
1105
|
|
|
|
|
|
|
if ( any { $_ eq $player } @pair ) { |
1106
|
|
|
|
|
|
|
if ( any { $_ eq $opponent } @pair ) { |
1107
|
|
|
|
|
|
|
return { $n => $table }; |
1108
|
|
|
|
|
|
|
} |
1109
|
|
|
|
|
|
|
} |
1110
|
|
|
|
|
|
|
} |
1111
|
|
|
|
|
|
|
die "No table with player $player, opponent $opponent in round $round"; |
1112
|
|
|
|
|
|
|
} |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
=head3 compQuizfile |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
The file system location of the file with the quiz questions and answers for the given Compcomp activity. |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
=cut |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
method compQuizfile ( Str $round ) { |
1121
|
|
|
|
|
|
|
my $config = $self->config($round); |
1122
|
|
|
|
|
|
|
my $text = $config->{text}; |
1123
|
|
|
|
|
|
|
return $self->compcompdirs . "/../" . $text; |
1124
|
|
|
|
|
|
|
} |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
=head3 topicNames |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
Returns the names of comp quiz topics as an arrayref. |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
=cut |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
method topicNames ( Str $round ) { |
1133
|
|
|
|
|
|
|
my $config = $self->config($round); |
1134
|
|
|
|
|
|
|
my $activities = $config->{activity}; |
1135
|
|
|
|
|
|
|
my @topics = keys %$activities; |
1136
|
|
|
|
|
|
|
return \@topics; |
1137
|
|
|
|
|
|
|
} |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
=head3 compQuizAttempted |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
Returns the comp quiz topics and their associated forms attempted by the given group in the round, as an arrayref of hashrefs keyed on 'topic' and 'form'. |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
=cut |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
method compQuizAttempted ( Str $round, Str $table ) { |
1146
|
|
|
|
|
|
|
my $config = $self->config($round); |
1147
|
|
|
|
|
|
|
my $activities = $config->{activity}; |
1148
|
|
|
|
|
|
|
my $selection = $self->compQuizSelection; |
1149
|
|
|
|
|
|
|
my $attempted; |
1150
|
|
|
|
|
|
|
for my $topic ( keys %$selection ) { |
1151
|
|
|
|
|
|
|
my $forms = $selection->{$topic}; |
1152
|
|
|
|
|
|
|
for my $form ( keys %$forms ) { |
1153
|
|
|
|
|
|
|
my $tables = $activities->{$topic}->{$form}; |
1154
|
|
|
|
|
|
|
push @$attempted, { topic => $topic, form => $form } |
1155
|
|
|
|
|
|
|
if any { $table == $_ } @$tables; |
1156
|
|
|
|
|
|
|
} |
1157
|
|
|
|
|
|
|
} |
1158
|
|
|
|
|
|
|
return $attempted; |
1159
|
|
|
|
|
|
|
} |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
=head3 compQuiz |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
The compQuiz questions (as an anon array) in the given Compcomp activity for the given table. |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
=cut |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
method compQuiz ( Str $round, Str $table ) { |
1168
|
|
|
|
|
|
|
my $quizfile = $self->compQuizfile($round); |
1169
|
|
|
|
|
|
|
my $activity; |
1170
|
|
|
|
|
|
|
try { $activity = $self->inspect( $quizfile ) } |
1171
|
|
|
|
|
|
|
catch { warn "No $quizfile Compcomp content file" }; |
1172
|
|
|
|
|
|
|
my $topic = $self->compTopic( $round, $table ); |
1173
|
|
|
|
|
|
|
my $form = $self->compForm( $round, $table ); |
1174
|
|
|
|
|
|
|
my $quiz = $activity->{$topic}->{compcomp}->{$form}->{quiz}; |
1175
|
|
|
|
|
|
|
carp "No $topic, $form quiz in $quizfile," unless $quiz; |
1176
|
|
|
|
|
|
|
return $quiz; |
1177
|
|
|
|
|
|
|
} |
1178
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
=head3 compTopic |
1180
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
The topic of the quiz in the given Compcomp round for the given table. Each table has one and only one quiz. |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
=cut |
1184
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
method compTopic ( Str $round, Str $table ) { |
1186
|
|
|
|
|
|
|
my $config = $self->config($round); |
1187
|
|
|
|
|
|
|
my $activity = $config->{activity}; |
1188
|
|
|
|
|
|
|
for my $topic ( keys %$activity ) { |
1189
|
|
|
|
|
|
|
my $forms = $activity->{$topic}; |
1190
|
|
|
|
|
|
|
for my $form ( keys %$forms ) { |
1191
|
|
|
|
|
|
|
my $tables = $forms->{$form}; |
1192
|
|
|
|
|
|
|
return $topic if any { $_ eq $table } @$tables; |
1193
|
|
|
|
|
|
|
} |
1194
|
|
|
|
|
|
|
} |
1195
|
|
|
|
|
|
|
carp "Topic? No quiz at table $table in round $round,"; |
1196
|
|
|
|
|
|
|
return; |
1197
|
|
|
|
|
|
|
} |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
=head3 compTopics |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
The topics of the quiz in the given Compcomp round for the given table, as an array ref. |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
=cut |
1204
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
method compTopics ( Str $round, Str $table ) { |
1206
|
|
|
|
|
|
|
my $config = $self->config($round); |
1207
|
|
|
|
|
|
|
my $activity = $config->{activity}; |
1208
|
|
|
|
|
|
|
my %topics; |
1209
|
|
|
|
|
|
|
for my $topic ( keys %$activity ) { |
1210
|
|
|
|
|
|
|
my $forms = $activity->{$topic}; |
1211
|
|
|
|
|
|
|
for my $form ( keys %$forms ) { |
1212
|
|
|
|
|
|
|
my $tables = $forms->{$form}; |
1213
|
|
|
|
|
|
|
$topics{ $topic } += 1 if any { $_ eq $table } @$tables; |
1214
|
|
|
|
|
|
|
} |
1215
|
|
|
|
|
|
|
} |
1216
|
|
|
|
|
|
|
carp "Topic? No quiz at table $table in round $round," unless %topics; |
1217
|
|
|
|
|
|
|
my @topics = keys %topics; |
1218
|
|
|
|
|
|
|
return \@topics; |
1219
|
|
|
|
|
|
|
} |
1220
|
|
|
|
|
|
|
|
1221
|
|
|
|
|
|
|
=head3 compForm |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
The form of the quiz in the given Compcomp round for the given table. Each table has one and only one quiz. |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
=cut |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
method compForm ( Str $round, Str $table ) { |
1228
|
|
|
|
|
|
|
my $config = $self->config($round); |
1229
|
|
|
|
|
|
|
my $activity = $config->{activity}; |
1230
|
|
|
|
|
|
|
for my $topic ( keys %$activity ) { |
1231
|
|
|
|
|
|
|
my $forms = $activity->{$topic}; |
1232
|
|
|
|
|
|
|
for my $form ( keys %$forms ) { |
1233
|
|
|
|
|
|
|
my $tables = $forms->{$form}; |
1234
|
|
|
|
|
|
|
return $form if any { $_ eq $table } @$tables; |
1235
|
|
|
|
|
|
|
} |
1236
|
|
|
|
|
|
|
} |
1237
|
|
|
|
|
|
|
carp "Form? No quiz at table $table in round $round,"; |
1238
|
|
|
|
|
|
|
return; |
1239
|
|
|
|
|
|
|
} |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
=head3 compForms |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
The forms in the given Compcomp round for the given table, in the given quiz (topic), as an array ref. |
1244
|
|
|
|
|
|
|
|
1245
|
|
|
|
|
|
|
=cut |
1246
|
|
|
|
|
|
|
|
1247
|
|
|
|
|
|
|
method compForms ( Str $round, Str $table, Str $topic ) { |
1248
|
|
|
|
|
|
|
my $config = $self->config($round); |
1249
|
|
|
|
|
|
|
my $activity = $config->{activity}; |
1250
|
|
|
|
|
|
|
my $forms = $activity->{$topic}; |
1251
|
|
|
|
|
|
|
my @forms; |
1252
|
|
|
|
|
|
|
for my $form ( keys %$forms ) { |
1253
|
|
|
|
|
|
|
my $tables = $forms->{$form}; |
1254
|
|
|
|
|
|
|
push @forms, $form if any { $_ eq $table } @$tables; |
1255
|
|
|
|
|
|
|
} |
1256
|
|
|
|
|
|
|
carp "Form? No quiz at table $table in round $round," unless @forms; |
1257
|
|
|
|
|
|
|
return \@forms; |
1258
|
|
|
|
|
|
|
} |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
=head3 compqn |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
The number of questions in the given Compcomp quiz for the given pair. |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
=cut |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
method compqn ( Str $round, Str $table ) { |
1267
|
|
|
|
|
|
|
my $quiz = $self->compQuiz( $round, $table ); |
1268
|
|
|
|
|
|
|
return scalar @$quiz; |
1269
|
|
|
|
|
|
|
} |
1270
|
|
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
=head3 idsbyCompRole |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
Ids in array, in White, Black role order |
1274
|
|
|
|
|
|
|
|
1275
|
|
|
|
|
|
|
=cut |
1276
|
|
|
|
|
|
|
|
1277
|
|
|
|
|
|
|
|
1278
|
|
|
|
|
|
|
method idsbyCompRole ( Str $round, Str $table ) { |
1279
|
|
|
|
|
|
|
my $members = $self->league->members; |
1280
|
|
|
|
|
|
|
my %namedMembers = map { $_->{name} => $_ } @$members; |
1281
|
|
|
|
|
|
|
my $config = $self->config( $round ); |
1282
|
|
|
|
|
|
|
my $pair = $config->{group}->{$table}; |
1283
|
|
|
|
|
|
|
my @idsbyRole = @$pair{qw/White Black/}; |
1284
|
|
|
|
|
|
|
return \@idsbyRole; |
1285
|
|
|
|
|
|
|
} |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
=head3 scores |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
The scores at the tables of the tournament in the given round (as an anon hash keyed on the ids of the members). In a file in the Compcomp round directory called 'result.yaml'. |
1290
|
|
|
|
|
|
|
|
1291
|
|
|
|
|
|
|
=cut |
1292
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
|
1294
|
|
|
|
|
|
|
method scores ( Str $round ) { |
1295
|
|
|
|
|
|
|
my $comp = $self->compcompdirs; |
1296
|
|
|
|
|
|
|
my $file = "$comp/$round/scores.yaml"; |
1297
|
|
|
|
|
|
|
my $results = $self->inspect( $file ); |
1298
|
|
|
|
|
|
|
return $results; |
1299
|
|
|
|
|
|
|
} |
1300
|
|
|
|
|
|
|
|
1301
|
|
|
|
|
|
|
=head3 compResponses |
1302
|
|
|
|
|
|
|
|
1303
|
|
|
|
|
|
|
The responses of the members of the given pair in the given round (as an anon hash keyed on the ids of the members). In a file in the Compcomp round directory called 'response.yaml'. |
1304
|
|
|
|
|
|
|
|
1305
|
|
|
|
|
|
|
=cut |
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
method compResponses ( Str $round, Str $table ) { |
1309
|
|
|
|
|
|
|
my $comp = $self->compcompdirs; |
1310
|
|
|
|
|
|
|
my $file = "$comp/$round/response.yaml"; |
1311
|
|
|
|
|
|
|
my $responses = $self->inspect( $file ); |
1312
|
|
|
|
|
|
|
return { free => $responses->{free}->{$table}, |
1313
|
|
|
|
|
|
|
set => $responses->{set}->{$table} }; |
1314
|
|
|
|
|
|
|
} |
1315
|
|
|
|
|
|
|
|
1316
|
|
|
|
|
|
|
=head3 freeTotals |
1317
|
|
|
|
|
|
|
|
1318
|
|
|
|
|
|
|
The number of free questions each asked by White and Black. |
1319
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
=cut |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
|
1323
|
|
|
|
|
|
|
method freeTotals ( Str $round, Str $table ) { |
1324
|
|
|
|
|
|
|
my $response = $self->compResponses( $round, $table ); |
1325
|
|
|
|
|
|
|
my $player = $self->idsbyCompRole( $round, $table ); |
1326
|
|
|
|
|
|
|
my $topics = $self->compTopics( $round, $table ); |
1327
|
|
|
|
|
|
|
my @qn = (0,0); |
1328
|
|
|
|
|
|
|
for my $topic ( @$topics ) { |
1329
|
|
|
|
|
|
|
my $forms = $self->compForms( $round, $table, $topic ); |
1330
|
|
|
|
|
|
|
for my $form ( @$forms ) { |
1331
|
|
|
|
|
|
|
for my $n ( 0,1 ) { |
1332
|
|
|
|
|
|
|
my $points = |
1333
|
|
|
|
|
|
|
$response->{free}->{$topic}->{$form}->{$player->[$n]}->{point}; |
1334
|
|
|
|
|
|
|
$qn[$n] += max ( grep { $points->{$_} ne 'Nil' } |
1335
|
|
|
|
|
|
|
keys %$points ) || 0; |
1336
|
|
|
|
|
|
|
} |
1337
|
|
|
|
|
|
|
} |
1338
|
|
|
|
|
|
|
} |
1339
|
|
|
|
|
|
|
return \@qn; |
1340
|
|
|
|
|
|
|
} |
1341
|
|
|
|
|
|
|
|
1342
|
|
|
|
|
|
|
=head3 lowerFreeTotal |
1343
|
|
|
|
|
|
|
|
1344
|
|
|
|
|
|
|
The lesser of the 2 numbers of free questions asked by either White and Black. |
1345
|
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
=cut |
1347
|
|
|
|
|
|
|
|
1348
|
|
|
|
|
|
|
method lowerFreeTotal ( Str $round, Str $table ) { |
1349
|
|
|
|
|
|
|
my $totals = $self->freeTotals( $round, $table ); |
1350
|
|
|
|
|
|
|
return min @$totals; |
1351
|
|
|
|
|
|
|
} |
1352
|
|
|
|
|
|
|
|
1353
|
|
|
|
|
|
|
=head3 byer |
1354
|
|
|
|
|
|
|
|
1355
|
|
|
|
|
|
|
The id of the player with the Bye, or the empty string. |
1356
|
|
|
|
|
|
|
|
1357
|
|
|
|
|
|
|
=cut |
1358
|
|
|
|
|
|
|
|
1359
|
|
|
|
|
|
|
method byer ( Str $round ) { |
1360
|
|
|
|
|
|
|
my $config = $self->config( $round ); |
1361
|
|
|
|
|
|
|
my $byer = $config->{bye}; |
1362
|
|
|
|
|
|
|
return $byer if $byer; |
1363
|
|
|
|
|
|
|
return ''; |
1364
|
|
|
|
|
|
|
} |
1365
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
|
1367
|
|
|
|
|
|
|
=head3 transfer |
1368
|
|
|
|
|
|
|
|
1369
|
|
|
|
|
|
|
An array ref of the ids of the players who were playing in another league in the round, or the empty string. |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
=cut |
1372
|
|
|
|
|
|
|
|
1373
|
|
|
|
|
|
|
method transfer ( Str $round ) { |
1374
|
|
|
|
|
|
|
my $config = $self->config( $round ); |
1375
|
|
|
|
|
|
|
my $transfers = $config->{transfer} || ''; |
1376
|
|
|
|
|
|
|
return $transfers; |
1377
|
|
|
|
|
|
|
} |
1378
|
|
|
|
|
|
|
|
1379
|
|
|
|
|
|
|
|
1380
|
|
|
|
|
|
|
=head3 opponents |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
The ids of opponents of the players in the given conversation. |
1383
|
|
|
|
|
|
|
|
1384
|
|
|
|
|
|
|
=cut |
1385
|
|
|
|
|
|
|
|
1386
|
|
|
|
|
|
|
method opponents ( Str $round ) { |
1387
|
|
|
|
|
|
|
my $tables = $self->tables( $round ); |
1388
|
|
|
|
|
|
|
my %opponent; |
1389
|
|
|
|
|
|
|
for my $n ( keys %$tables ) { |
1390
|
|
|
|
|
|
|
$opponent{$tables->{$n}->{White}} = $tables->{$n}->{Black}; |
1391
|
|
|
|
|
|
|
$opponent{$tables->{$n}->{Black}} = $tables->{$n}->{White}; |
1392
|
|
|
|
|
|
|
} |
1393
|
|
|
|
|
|
|
my $byer = $self->byer( $round ); |
1394
|
|
|
|
|
|
|
$opponent{ $byer } = 'bye' if $byer; |
1395
|
|
|
|
|
|
|
my $transfers = $self->transfer( $round ); |
1396
|
|
|
|
|
|
|
@opponent{ @$transfers } = ( 'transfer' ) x @$transfers |
1397
|
|
|
|
|
|
|
if ( $transfers and ref( $transfers ) eq 'ARRAY' ); |
1398
|
|
|
|
|
|
|
my $league = $self->league; |
1399
|
|
|
|
|
|
|
my $members = $league->members; |
1400
|
|
|
|
|
|
|
$opponent{$_->{id}} ||= 'unpaired' for @$members; |
1401
|
|
|
|
|
|
|
return \%opponent; |
1402
|
|
|
|
|
|
|
} |
1403
|
|
|
|
|
|
|
|
1404
|
|
|
|
|
|
|
|
1405
|
|
|
|
|
|
|
=head3 correct |
1406
|
|
|
|
|
|
|
|
1407
|
|
|
|
|
|
|
The number of questions correct in the given conversation. |
1408
|
|
|
|
|
|
|
|
1409
|
|
|
|
|
|
|
=cut |
1410
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
method correct ( Str $round ) { |
1412
|
|
|
|
|
|
|
my $comp = $self->compcompdirs; |
1413
|
|
|
|
|
|
|
my $file = "$comp/$round/scores.yaml"; |
1414
|
|
|
|
|
|
|
my $tables = $self->inspect( $file ); |
1415
|
|
|
|
|
|
|
my %correct; |
1416
|
|
|
|
|
|
|
for my $table ( keys %$tables ) { |
1417
|
|
|
|
|
|
|
my $scores = $tables->{$table}; |
1418
|
|
|
|
|
|
|
@correct{keys %$scores} = values %$scores; |
1419
|
|
|
|
|
|
|
} |
1420
|
|
|
|
|
|
|
return \%correct; |
1421
|
|
|
|
|
|
|
} |
1422
|
|
|
|
|
|
|
|
1423
|
|
|
|
|
|
|
|
1424
|
|
|
|
|
|
|
=head3 assistantPoints |
1425
|
|
|
|
|
|
|
|
1426
|
|
|
|
|
|
|
Assistants points are from config->{assistant} of form { Black => { U9933002 => 3, U9933007 => 4}, Yellow => { U9931007 => 4, U9933022 => 4 } }, and are the points for examiners with other responsibilities who are not participating in the round. |
1427
|
|
|
|
|
|
|
|
1428
|
|
|
|
|
|
|
=cut |
1429
|
|
|
|
|
|
|
|
1430
|
|
|
|
|
|
|
method assistantPoints ( Str $round ) { |
1431
|
|
|
|
|
|
|
my $config = $self->config( $round ); |
1432
|
|
|
|
|
|
|
my $assistants = $config->{assistant}; |
1433
|
|
|
|
|
|
|
if ( $assistants ) { |
1434
|
|
|
|
|
|
|
my %assistantPoints = map { %{ $assistants->{$_} } } keys %$assistants; |
1435
|
|
|
|
|
|
|
# my %assistantPoints = map { $assistants->{$_}->flatten } keys %$assistants; |
1436
|
|
|
|
|
|
|
die "@{ [keys %$assistants] }: assistant member mistakes." if any |
1437
|
|
|
|
|
|
|
{ not $self->league->is_member($_) } keys %assistantPoints; |
1438
|
|
|
|
|
|
|
return \%assistantPoints; |
1439
|
|
|
|
|
|
|
} |
1440
|
|
|
|
|
|
|
} |
1441
|
|
|
|
|
|
|
|
1442
|
|
|
|
|
|
|
=head3 dispensation |
1443
|
|
|
|
|
|
|
|
1444
|
|
|
|
|
|
|
Dispensation points are from config->{dispensation} of same form as assistantPoints, { Black => { U9933002 => 3, U9933007 => 4}, Yellow => { U9931007 => 4, U9933022 => 4 } }. |
1445
|
|
|
|
|
|
|
|
1446
|
|
|
|
|
|
|
=cut |
1447
|
|
|
|
|
|
|
|
1448
|
|
|
|
|
|
|
method dispensation ( Str $round ) { |
1449
|
|
|
|
|
|
|
my $config = $self->config( $round ); |
1450
|
|
|
|
|
|
|
my $dispensation = $config->{dispensation}; |
1451
|
|
|
|
|
|
|
if ( $dispensation ) { |
1452
|
|
|
|
|
|
|
my %dispensation = map { %{ $dispensation->{$_} } } keys %$dispensation; |
1453
|
|
|
|
|
|
|
# my %assistantPoints = map { $assistants->{$_}->flatten } keys %$assistants; |
1454
|
|
|
|
|
|
|
die "@{ [keys %$dispensation] }: members?" if any |
1455
|
|
|
|
|
|
|
{ not $self->league->is_member($_) } keys %dispensation; |
1456
|
|
|
|
|
|
|
return \%dispensation; |
1457
|
|
|
|
|
|
|
} |
1458
|
|
|
|
|
|
|
} |
1459
|
|
|
|
|
|
|
|
1460
|
|
|
|
|
|
|
=head3 payout |
1461
|
|
|
|
|
|
|
|
1462
|
|
|
|
|
|
|
If payprotocol field is 'meritPay', 1 question each: 0,1 or 2 pts. 2 question each: 1,2 or 3 pts. 3 question each: 2,3 or 4 pts. 4 question each: 3,4 or 5 pts. |
1463
|
|
|
|
|
|
|
|
1464
|
|
|
|
|
|
|
If the 'meritPay' payprotocol field ends in a number the specified number of questions each is required for the maximum points. |
1465
|
|
|
|
|
|
|
=cut |
1466
|
|
|
|
|
|
|
|
1467
|
|
|
|
|
|
|
method payout ( Str $player, Str $opponent, Str $round ) { |
1468
|
|
|
|
|
|
|
my $protocol = $self->config($round)->{payprotocol}; |
1469
|
|
|
|
|
|
|
my ($loss, $draw, $win) = (3,4,5); |
1470
|
|
|
|
|
|
|
if ( defined $protocol and $protocol =~ m/^meritPay/ ) { |
1471
|
|
|
|
|
|
|
(my $top_number = $protocol ) =~ s/^\D*(\d*)$/$1/; |
1472
|
|
|
|
|
|
|
my $required = $top_number? $top_number: 4; |
1473
|
|
|
|
|
|
|
my $table = $self->pair2table( $player, $opponent, $round ); |
1474
|
|
|
|
|
|
|
my $tableN = (keys %$table)[0]; |
1475
|
|
|
|
|
|
|
my $questionN = $self->lowerFreeTotal( $round, $tableN ); |
1476
|
|
|
|
|
|
|
my $unfulfilled = $required - $questionN; |
1477
|
|
|
|
|
|
|
if ( $unfulfilled > 0 ) { |
1478
|
|
|
|
|
|
|
$_ -= $unfulfilled for ($loss, $draw, $win); |
1479
|
|
|
|
|
|
|
if ( $loss < 0 ) { |
1480
|
|
|
|
|
|
|
$loss = 0; $draw = 0; $win = 1; |
1481
|
|
|
|
|
|
|
} |
1482
|
|
|
|
|
|
|
} |
1483
|
|
|
|
|
|
|
} |
1484
|
|
|
|
|
|
|
return { loss => $loss, draw => $draw, win => $win }; |
1485
|
|
|
|
|
|
|
} |
1486
|
|
|
|
|
|
|
|
1487
|
|
|
|
|
|
|
|
1488
|
|
|
|
|
|
|
=head3 points |
1489
|
|
|
|
|
|
|
|
1490
|
|
|
|
|
|
|
The points of the players in the given conversation. 5 for a Bye, 1 for Late, 0 for Unpaired, 1 for a non-numerical number correct result, 5 for more correct, 3 for less correct, 4 for the same number correct. Transfers' results are computed from their results in the same round in their old league. Assistants points are from round.yaml, points for non-paired helpers. |
1491
|
|
|
|
|
|
|
|
1492
|
|
|
|
|
|
|
=cut |
1493
|
|
|
|
|
|
|
|
1494
|
|
|
|
|
|
|
method points ( Str $round ) { |
1495
|
|
|
|
|
|
|
my $config = $self->config( $round ); |
1496
|
|
|
|
|
|
|
my $opponents = $self->opponents( $round ); |
1497
|
|
|
|
|
|
|
my $correct = $self->correct( $round ); |
1498
|
|
|
|
|
|
|
my $points; |
1499
|
|
|
|
|
|
|
my $late; $late = $config->{late} if exists $config->{late}; |
1500
|
|
|
|
|
|
|
my $forfeit; $forfeit = $config->{forfeit} if exists $config->{forfeit}; |
1501
|
|
|
|
|
|
|
my $assists = $self->assistantPoints( $round ); |
1502
|
|
|
|
|
|
|
my $dispensed = $self->dispensation( $round ); |
1503
|
|
|
|
|
|
|
my $byer = $self->byer( $round ); |
1504
|
|
|
|
|
|
|
PLAYER: for my $player ( keys %$opponents ) { |
1505
|
|
|
|
|
|
|
if ( defined $assists and any { $_ eq $player } keys %$assists){ |
1506
|
|
|
|
|
|
|
$points->{$player} = $assists->{$player}; |
1507
|
|
|
|
|
|
|
next PLAYER; |
1508
|
|
|
|
|
|
|
} |
1509
|
|
|
|
|
|
|
if ( defined $dispensed and any { $_ eq $player } keys %$dispensed){ |
1510
|
|
|
|
|
|
|
$points->{$player} = $dispensed->{$player}; |
1511
|
|
|
|
|
|
|
next PLAYER; |
1512
|
|
|
|
|
|
|
} |
1513
|
|
|
|
|
|
|
if ( any { defined } @$forfeit and any { $_ eq $player } @$forfeit){ |
1514
|
|
|
|
|
|
|
$points->{$player} = 0; |
1515
|
|
|
|
|
|
|
next PLAYER; |
1516
|
|
|
|
|
|
|
} |
1517
|
|
|
|
|
|
|
if ( any { defined } @$late and any { $_ eq $player } @$late ) { |
1518
|
|
|
|
|
|
|
$points->{$player} = 1; |
1519
|
|
|
|
|
|
|
next PLAYER; |
1520
|
|
|
|
|
|
|
} |
1521
|
|
|
|
|
|
|
if ( $byer and $player eq $byer ) { |
1522
|
|
|
|
|
|
|
$points->{$player} = 5; |
1523
|
|
|
|
|
|
|
next PLAYER; |
1524
|
|
|
|
|
|
|
} |
1525
|
|
|
|
|
|
|
if ( $opponents->{$player} =~ m/unpaired/i ) { |
1526
|
|
|
|
|
|
|
$points->{$player} = 0; |
1527
|
|
|
|
|
|
|
next PLAYER; |
1528
|
|
|
|
|
|
|
} |
1529
|
|
|
|
|
|
|
if ( $opponents->{$player} =~ m/transfer/i ) { |
1530
|
|
|
|
|
|
|
my $oldleagueId = $self->league->transfer->{$player}; |
1531
|
|
|
|
|
|
|
my $oldleague = League->new( id => $oldleagueId ); |
1532
|
|
|
|
|
|
|
my $oldgrades = Grades->new({ league => $oldleague }); |
1533
|
|
|
|
|
|
|
my $oldclasswork = $oldgrades->classwork; |
1534
|
|
|
|
|
|
|
$points->{$player} = $oldclasswork->points($round)->{$player}; |
1535
|
|
|
|
|
|
|
next PLAYER; |
1536
|
|
|
|
|
|
|
} |
1537
|
|
|
|
|
|
|
my $other = $opponents->{$player}; |
1538
|
|
|
|
|
|
|
my $alterego = $opponents->{$other}; |
1539
|
|
|
|
|
|
|
die |
1540
|
|
|
|
|
|
|
"${player}'s opponent is $other, but ${other}'s opponent is $alterego" |
1541
|
|
|
|
|
|
|
unless $other and $alterego and $player eq $alterego; |
1542
|
|
|
|
|
|
|
die "No $player quiz card in round $round?" unless exists |
1543
|
|
|
|
|
|
|
$correct->{$player}; |
1544
|
|
|
|
|
|
|
my $ourcorrect = $correct->{$player}; |
1545
|
|
|
|
|
|
|
die "No $other card against $player in round $round?" unless |
1546
|
|
|
|
|
|
|
exists $correct->{$other}; |
1547
|
|
|
|
|
|
|
my $theircorrect = $correct->{$other}; |
1548
|
|
|
|
|
|
|
if ( not defined $ourcorrect ) { |
1549
|
|
|
|
|
|
|
$points->{$player} = 0; |
1550
|
|
|
|
|
|
|
next PLAYER; |
1551
|
|
|
|
|
|
|
} |
1552
|
|
|
|
|
|
|
if ( $correct->{$player} !~ m/^\d+$/ ) { |
1553
|
|
|
|
|
|
|
$points->{$player} = 1; |
1554
|
|
|
|
|
|
|
next PLAYER; |
1555
|
|
|
|
|
|
|
} |
1556
|
|
|
|
|
|
|
if ( any { defined } @$forfeit and any { $_ eq $other } @$forfeit) { |
1557
|
|
|
|
|
|
|
$points->{$player} = 5; |
1558
|
|
|
|
|
|
|
next PLAYER; |
1559
|
|
|
|
|
|
|
} |
1560
|
|
|
|
|
|
|
my $grade = $self->payout( $player, $other, $round ); |
1561
|
|
|
|
|
|
|
$points->{$player} = $ourcorrect > $theircorrect? $grade->{win}: |
1562
|
|
|
|
|
|
|
$ourcorrect < $theircorrect? $grade->{loss}: $grade->{draw}; |
1563
|
|
|
|
|
|
|
} |
1564
|
|
|
|
|
|
|
return $points; |
1565
|
|
|
|
|
|
|
} |
1566
|
|
|
|
|
|
|
|
1567
|
|
|
|
|
|
|
|
1568
|
|
|
|
|
|
|
=head3 total |
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
The total over the conversations over the series. |
1571
|
|
|
|
|
|
|
|
1572
|
|
|
|
|
|
|
=cut |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
has 'total' => ( is => 'ro', isa => Results, lazy_build => 1 ); |
1575
|
|
|
|
|
|
|
method _build_total { |
1576
|
|
|
|
|
|
|
my $rounds = $self->all_events; |
1577
|
|
|
|
|
|
|
my $members = $self->league->members; |
1578
|
|
|
|
|
|
|
my @ids = map { $_->{id} } @$members; |
1579
|
|
|
|
|
|
|
my $totals; |
1580
|
|
|
|
|
|
|
@$totals{ @ids } = (0) x @ids; |
1581
|
|
|
|
|
|
|
for my $round ( @$rounds ) { |
1582
|
|
|
|
|
|
|
my $points = $self->points( $round ); |
1583
|
|
|
|
|
|
|
for my $id ( @ids ) { |
1584
|
|
|
|
|
|
|
next unless defined $points->{$id}; |
1585
|
|
|
|
|
|
|
$totals->{$id} += $points->{$id}; |
1586
|
|
|
|
|
|
|
} |
1587
|
|
|
|
|
|
|
} |
1588
|
|
|
|
|
|
|
return $totals; |
1589
|
|
|
|
|
|
|
} |
1590
|
|
|
|
|
|
|
|
1591
|
|
|
|
|
|
|
|
1592
|
|
|
|
|
|
|
=head3 totalPercent |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
The total over the conversations over the series expressed as a percentage of the possible score. The average should be 80 percent if every player participates in every comp. |
1595
|
|
|
|
|
|
|
|
1596
|
|
|
|
|
|
|
=cut |
1597
|
|
|
|
|
|
|
|
1598
|
|
|
|
|
|
|
has 'totalPercent' => ( is => 'ro', isa => Results, lazy_build => 1 ); |
1599
|
|
|
|
|
|
|
method _build_totalPercent { |
1600
|
|
|
|
|
|
|
my $rounds = $self->all_events; |
1601
|
|
|
|
|
|
|
my $n = scalar @$rounds; |
1602
|
|
|
|
|
|
|
my $totals = $self->total; |
1603
|
|
|
|
|
|
|
my %percentages = $n? |
1604
|
|
|
|
|
|
|
map { $_ => $totals->{$_} * 100 / (5*$n) } keys %$totals: |
1605
|
|
|
|
|
|
|
map { $_ => 0 } keys %$totals; |
1606
|
|
|
|
|
|
|
return \%percentages; |
1607
|
|
|
|
|
|
|
} |
1608
|
|
|
|
|
|
|
|
1609
|
|
|
|
|
|
|
} |
1610
|
|
|
|
|
|
|
|
1611
|
|
|
|
|
|
|
|
1612
|
|
|
|
|
|
|
=head2 Grades' Exams Methods |
1613
|
|
|
|
|
|
|
=cut |
1614
|
|
|
|
|
|
|
|
1615
|
|
|
|
|
|
|
role Exams { |
1616
|
|
|
|
|
|
|
use List::Util qw/max sum/; |
1617
|
|
|
|
|
|
|
use List::MoreUtils qw/any all/; |
1618
|
|
|
|
|
|
|
use Carp; |
1619
|
|
|
|
|
|
|
use Grades::Types qw/Exam/; |
1620
|
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
=head3 examdirs |
1622
|
|
|
|
|
|
|
|
1623
|
|
|
|
|
|
|
The directory where the exams are. |
1624
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
=cut |
1626
|
|
|
|
|
|
|
|
1627
|
|
|
|
|
|
|
has 'examdirs' => (is => 'ro', isa => 'Str', lazy_build => 1); |
1628
|
|
|
|
|
|
|
method _build_examdirs { |
1629
|
|
|
|
|
|
|
my $league = $self->league->id; |
1630
|
|
|
|
|
|
|
my $leaguedir = $self->league->leagues . "/" . $league; |
1631
|
|
|
|
|
|
|
my $basename = $self->league->yaml->{jigsaw} || |
1632
|
|
|
|
|
|
|
$self->league->yaml->{exams} || "exams"; |
1633
|
|
|
|
|
|
|
my $examdirs = $leaguedir .'/' . $basename; |
1634
|
|
|
|
|
|
|
} |
1635
|
|
|
|
|
|
|
|
1636
|
|
|
|
|
|
|
=head3 examids |
1637
|
|
|
|
|
|
|
|
1638
|
|
|
|
|
|
|
An arrayref of the ids of the exams for which there are grades for players in the league, in numerical order, of the form, [1, 3 .. 7, 9, 10 .. 99 ]. Results are in sub directories of the same name, under examdir. |
1639
|
|
|
|
|
|
|
|
1640
|
|
|
|
|
|
|
=cut |
1641
|
|
|
|
|
|
|
|
1642
|
|
|
|
|
|
|
has 'examids', |
1643
|
|
|
|
|
|
|
( is => 'ro', isa => 'Maybe[ArrayRef[Int]]', lazy_build => 1 ); |
1644
|
|
|
|
|
|
|
method _build_examids { |
1645
|
|
|
|
|
|
|
my $examdirs = $self->examdirs; |
1646
|
|
|
|
|
|
|
my @exams = grep { -d } glob "$examdirs/[0-9] $examdirs/[1-9][0-9]"; |
1647
|
|
|
|
|
|
|
[ sort { $a <=> $b } map m/^$examdirs\/(\d+)$/, @exams ]; |
1648
|
|
|
|
|
|
|
} |
1649
|
|
|
|
|
|
|
|
1650
|
|
|
|
|
|
|
=head3 examrounds |
1651
|
|
|
|
|
|
|
|
1652
|
|
|
|
|
|
|
The rounds over which the given exam was conducted. Should be an array ref. If there were no rounds, ie the exam was conducted in one round, a null anonymous array is returned. The results for the rounds are in sub directories underneath the 'examid' directory named, in numerical order, 1 .. 99. |
1653
|
|
|
|
|
|
|
|
1654
|
|
|
|
|
|
|
=cut |
1655
|
|
|
|
|
|
|
|
1656
|
|
|
|
|
|
|
method examrounds( Str $exam ) { |
1657
|
|
|
|
|
|
|
my $examdirs = $self->examdirs; |
1658
|
|
|
|
|
|
|
my $examids = $self->examids; |
1659
|
|
|
|
|
|
|
carp "No exam $exam in exams @$examids" |
1660
|
|
|
|
|
|
|
unless any { $_ eq $exam } @$examids; |
1661
|
|
|
|
|
|
|
my @rounds = glob "$examdirs/$exam/[0-9] $examdirs/$exam/[0-9][0-9]"; |
1662
|
|
|
|
|
|
|
[ sort { $a <=> $b } map m/^$examdirs\/$exam\/(\d+)$/, @rounds ]; |
1663
|
|
|
|
|
|
|
} |
1664
|
|
|
|
|
|
|
|
1665
|
|
|
|
|
|
|
=head3 examMax |
1666
|
|
|
|
|
|
|
|
1667
|
|
|
|
|
|
|
The maximum score possible in each individual exam. That is, what the exam is out of. |
1668
|
|
|
|
|
|
|
|
1669
|
|
|
|
|
|
|
=cut |
1670
|
|
|
|
|
|
|
|
1671
|
|
|
|
|
|
|
has 'examMax' => (is => 'ro', isa => 'Int', lazy => 1, required => 1, |
1672
|
|
|
|
|
|
|
default => sub { shift->league->yaml->{examMax} } ); |
1673
|
|
|
|
|
|
|
|
1674
|
|
|
|
|
|
|
=head3 exam |
1675
|
|
|
|
|
|
|
|
1676
|
|
|
|
|
|
|
$grades->exam($id) |
1677
|
|
|
|
|
|
|
|
1678
|
|
|
|
|
|
|
The scores of the players on an individual (round of an) exam (in a 'g.yaml file in the $id subdir of the league dir. |
1679
|
|
|
|
|
|
|
|
1680
|
|
|
|
|
|
|
=cut |
1681
|
|
|
|
|
|
|
|
1682
|
|
|
|
|
|
|
method exam ( Str $id ) { |
1683
|
|
|
|
|
|
|
my $examdirs = $self->examdirs; |
1684
|
|
|
|
|
|
|
my $exam = $self->inspect( "$examdirs/$id/g.yaml" ); |
1685
|
|
|
|
|
|
|
if ( is_Exam($exam) ) { |
1686
|
|
|
|
|
|
|
return $exam ; |
1687
|
|
|
|
|
|
|
} |
1688
|
|
|
|
|
|
|
else { |
1689
|
|
|
|
|
|
|
croak |
1690
|
|
|
|
|
|
|
"Exam $id probably has undefined or non-numeric Exam scores, or possibly illegal PlayerIds." ; |
1691
|
|
|
|
|
|
|
} |
1692
|
|
|
|
|
|
|
} |
1693
|
|
|
|
|
|
|
|
1694
|
|
|
|
|
|
|
=head3 examResults |
1695
|
|
|
|
|
|
|
|
1696
|
|
|
|
|
|
|
A hash ref of the ids of the players and arrays of their results over the exam series, ie examids, in files named 'g.yaml', TODO but only if such a file exists in all examdirs. Otherwise, calculate from raw 'response.yaml' files. Croak if any result is larger than examMax. |
1697
|
|
|
|
|
|
|
|
1698
|
|
|
|
|
|
|
=cut |
1699
|
|
|
|
|
|
|
|
1700
|
|
|
|
|
|
|
has 'examResults' => ( is => 'ro', isa => 'HashRef', lazy_build => 1 ); |
1701
|
|
|
|
|
|
|
method _build_examResults { |
1702
|
|
|
|
|
|
|
my $examids = $self->examids; |
1703
|
|
|
|
|
|
|
my $members = $self->league->members; |
1704
|
|
|
|
|
|
|
my @playerids = map { $_->{id} } @$members; |
1705
|
|
|
|
|
|
|
my %results; |
1706
|
|
|
|
|
|
|
for my $id ( @$examids ) { |
1707
|
|
|
|
|
|
|
my $exam = $self->exam( $id ); |
1708
|
|
|
|
|
|
|
my $max = $self->examMax; |
1709
|
|
|
|
|
|
|
for my $playerid ( @playerids ) { |
1710
|
|
|
|
|
|
|
my $result = $exam->{$playerid}; |
1711
|
|
|
|
|
|
|
carp "No exam $id results for $playerid," |
1712
|
|
|
|
|
|
|
unless defined $result; |
1713
|
|
|
|
|
|
|
croak "${playerid}'s $result greater than exam max, $max" |
1714
|
|
|
|
|
|
|
if defined $result and $result > $max; |
1715
|
|
|
|
|
|
|
my $results = $results{$playerid}; |
1716
|
|
|
|
|
|
|
push @$results, $result; |
1717
|
|
|
|
|
|
|
$results{$playerid} = $results; |
1718
|
|
|
|
|
|
|
} |
1719
|
|
|
|
|
|
|
} |
1720
|
|
|
|
|
|
|
return \%results; |
1721
|
|
|
|
|
|
|
} |
1722
|
|
|
|
|
|
|
|
1723
|
|
|
|
|
|
|
=head3 examResultHash |
1724
|
|
|
|
|
|
|
|
1725
|
|
|
|
|
|
|
A hash ref of the ids of the players and hashrefs of their results for each exam. Croak if any result is larger than examMax. |
1726
|
|
|
|
|
|
|
|
1727
|
|
|
|
|
|
|
=cut |
1728
|
|
|
|
|
|
|
|
1729
|
|
|
|
|
|
|
has 'examResultHash' => (is => 'ro', isa => 'HashRef', lazy_build => 1); |
1730
|
|
|
|
|
|
|
method _build_examResultHash { |
1731
|
|
|
|
|
|
|
my $examids = $self->examids; |
1732
|
|
|
|
|
|
|
my $examResults = $self->examResults; |
1733
|
|
|
|
|
|
|
my %examResults; |
1734
|
|
|
|
|
|
|
for my $id ( keys %$examResults ) { |
1735
|
|
|
|
|
|
|
my $results = $examResults->{$id}; |
1736
|
|
|
|
|
|
|
my %results; |
1737
|
|
|
|
|
|
|
@results{@$examids} = @$results; |
1738
|
|
|
|
|
|
|
$examResults{$id} = \%results; |
1739
|
|
|
|
|
|
|
} |
1740
|
|
|
|
|
|
|
return \%examResults; |
1741
|
|
|
|
|
|
|
} |
1742
|
|
|
|
|
|
|
|
1743
|
|
|
|
|
|
|
=head3 examResultsasPercent |
1744
|
|
|
|
|
|
|
|
1745
|
|
|
|
|
|
|
A hashref of the ids of the players and arrays of their results over the exams expressed as percentages of the maximum possible score for the exams. |
1746
|
|
|
|
|
|
|
|
1747
|
|
|
|
|
|
|
=cut |
1748
|
|
|
|
|
|
|
|
1749
|
|
|
|
|
|
|
has 'examResultsasPercent' => (is=>'ro', isa=>'HashRef', lazy_build=>1); |
1750
|
|
|
|
|
|
|
method _build_examResultsasPercent { |
1751
|
|
|
|
|
|
|
my $scores = $self->examResults; |
1752
|
|
|
|
|
|
|
my @ids = keys %$scores; |
1753
|
|
|
|
|
|
|
my $max = $self->examMax; |
1754
|
|
|
|
|
|
|
my %percent = map { my $id = $_; my $myscores = $scores->{$id}; |
1755
|
|
|
|
|
|
|
$id => [ map { ($_||0) * (100/$max) } @$myscores ] } @ids; |
1756
|
|
|
|
|
|
|
return \%percent; |
1757
|
|
|
|
|
|
|
} |
1758
|
|
|
|
|
|
|
|
1759
|
|
|
|
|
|
|
=head3 examGrade |
1760
|
|
|
|
|
|
|
|
1761
|
|
|
|
|
|
|
A hash ref of the ids of the players and their total scores on exams. |
1762
|
|
|
|
|
|
|
|
1763
|
|
|
|
|
|
|
=cut |
1764
|
|
|
|
|
|
|
|
1765
|
|
|
|
|
|
|
has 'examGrade' => (is => 'ro', isa => 'HashRef', lazy_build => 1); |
1766
|
|
|
|
|
|
|
method _build_examGrade { |
1767
|
|
|
|
|
|
|
my $grades = $self->examResults; |
1768
|
|
|
|
|
|
|
+{ map { my $numbers=$grades->{$_}; |
1769
|
|
|
|
|
|
|
$_ => sum(@$numbers) } |
1770
|
|
|
|
|
|
|
keys %$grades }; |
1771
|
|
|
|
|
|
|
} |
1772
|
|
|
|
|
|
|
|
1773
|
|
|
|
|
|
|
=head3 examPercent |
1774
|
|
|
|
|
|
|
|
1775
|
|
|
|
|
|
|
A hash ref of the ids of the players and their total score on exams, expressed as a percentage of the possible exam score. This is the average of their exam scores. |
1776
|
|
|
|
|
|
|
|
1777
|
|
|
|
|
|
|
=cut |
1778
|
|
|
|
|
|
|
|
1779
|
|
|
|
|
|
|
has 'examPercent' => (is => 'ro', isa => 'HashRef', lazy_build => 1); |
1780
|
|
|
|
|
|
|
method _build_examPercent { |
1781
|
|
|
|
|
|
|
my $grades = $self->examResultsasPercent; |
1782
|
|
|
|
|
|
|
my %totals = map { |
1783
|
|
|
|
|
|
|
my $numbers=$grades->{$_}; |
1784
|
|
|
|
|
|
|
$_ => sum(@$numbers)/@{$numbers} } keys %$grades; |
1785
|
|
|
|
|
|
|
return \%totals; |
1786
|
|
|
|
|
|
|
} |
1787
|
|
|
|
|
|
|
|
1788
|
|
|
|
|
|
|
} |
1789
|
|
|
|
|
|
|
|
1790
|
|
|
|
|
|
|
|
1791
|
|
|
|
|
|
|
=head2 Grades' Core Methods |
1792
|
|
|
|
|
|
|
|
1793
|
|
|
|
|
|
|
=cut |
1794
|
|
|
|
|
|
|
|
1795
|
|
|
|
|
|
|
class Grades with Homework with Exams with Jigsaw |
1796
|
|
|
|
|
|
|
|
1797
|
|
|
|
|
|
|
{ |
1798
|
|
|
|
|
|
|
# with 'Jigsaw' |
1799
|
|
|
|
|
|
|
# => { -alias => { config => 'jigsaw_config' }, -excludes => 'config' }; |
1800
|
|
|
|
|
|
|
require Grades::Groupwork; |
1801
|
|
|
|
|
|
|
use Carp; |
1802
|
|
|
|
|
|
|
use Grades::Types qw/Weights/; |
1803
|
|
|
|
|
|
|
|
1804
|
|
|
|
|
|
|
=head3 BUILDARGS |
1805
|
|
|
|
|
|
|
|
1806
|
|
|
|
|
|
|
Have Moose find out the classwork approach the league has adopted and create an object of that approach for the classwork accessor. This is preferable to requiring the user to create the object and pass it at construction time. |
1807
|
|
|
|
|
|
|
|
1808
|
|
|
|
|
|
|
=cut |
1809
|
|
|
|
|
|
|
|
1810
|
|
|
|
|
|
|
around BUILDARGS (ClassName $class: HashRef $args) { |
1811
|
|
|
|
|
|
|
my $league = $args->{league} or die "$args->{league} league?"; |
1812
|
|
|
|
|
|
|
my $approach = $league->approach or die "approach?"; |
1813
|
|
|
|
|
|
|
my $classwork = $approach->new( league => $league ) or die "classwork?"; |
1814
|
|
|
|
|
|
|
$args->{classwork} = $classwork; |
1815
|
|
|
|
|
|
|
return $class->$orig({ league => $league, classwork => $classwork }); |
1816
|
|
|
|
|
|
|
} |
1817
|
|
|
|
|
|
|
# around BUILDARGS(@args) { $self->$orig(@args) } |
1818
|
|
|
|
|
|
|
|
1819
|
|
|
|
|
|
|
=head3 classwork |
1820
|
|
|
|
|
|
|
|
1821
|
|
|
|
|
|
|
An accessor for the object that handles classwork methods. Required at construction time. |
1822
|
|
|
|
|
|
|
|
1823
|
|
|
|
|
|
|
=cut |
1824
|
|
|
|
|
|
|
|
1825
|
|
|
|
|
|
|
has 'classwork' => ( is => 'ro', isa => 'Approach', required => 1, |
1826
|
|
|
|
|
|
|
handles => [ 'series', 'beancans', |
1827
|
|
|
|
|
|
|
'points', 'all_events', |
1828
|
|
|
|
|
|
|
'classwork_total', 'classworkPercent' ] ); |
1829
|
|
|
|
|
|
|
|
1830
|
|
|
|
|
|
|
=head3 config |
1831
|
|
|
|
|
|
|
|
1832
|
|
|
|
|
|
|
The possible grades config files. Including Jigsaw, Compcomp. |
1833
|
|
|
|
|
|
|
|
1834
|
|
|
|
|
|
|
=cut |
1835
|
|
|
|
|
|
|
|
1836
|
|
|
|
|
|
|
method config ( $role, $round ) { |
1837
|
|
|
|
|
|
|
my $config = "${role}::config"; $self->$config( $round ); |
1838
|
|
|
|
|
|
|
} |
1839
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
=head3 league |
1841
|
|
|
|
|
|
|
|
1842
|
|
|
|
|
|
|
The league (object) whose grades these are. |
1843
|
|
|
|
|
|
|
|
1844
|
|
|
|
|
|
|
=cut |
1845
|
|
|
|
|
|
|
|
1846
|
|
|
|
|
|
|
has 'league' => (is =>'ro', isa => 'League', required => 1, |
1847
|
|
|
|
|
|
|
handles => [ 'inspect' ] ); |
1848
|
|
|
|
|
|
|
|
1849
|
|
|
|
|
|
|
=head3 weights |
1850
|
|
|
|
|
|
|
|
1851
|
|
|
|
|
|
|
An hash ref of the weights (expressed as a percentage) accorded to the three components, classwork, homework, and exams in the final grade. |
1852
|
|
|
|
|
|
|
|
1853
|
|
|
|
|
|
|
=cut |
1854
|
|
|
|
|
|
|
|
1855
|
|
|
|
|
|
|
has 'weights' => (is => 'ro', isa => Weights, lazy_build => 1 ); |
1856
|
|
|
|
|
|
|
method _build_weights { my $weights = $self->league->yaml->{weights}; } |
1857
|
|
|
|
|
|
|
|
1858
|
|
|
|
|
|
|
|
1859
|
|
|
|
|
|
|
=head3 sprintround |
1860
|
|
|
|
|
|
|
|
1861
|
|
|
|
|
|
|
sprintf( '%.0f', $number). sprintf warns if $number is undef. |
1862
|
|
|
|
|
|
|
|
1863
|
|
|
|
|
|
|
=cut |
1864
|
|
|
|
|
|
|
|
1865
|
|
|
|
|
|
|
method sprintround (Maybe[Num] $number) { |
1866
|
|
|
|
|
|
|
sprintf '%.0f', $number; |
1867
|
|
|
|
|
|
|
} |
1868
|
|
|
|
|
|
|
|
1869
|
|
|
|
|
|
|
=head3 grades |
1870
|
|
|
|
|
|
|
|
1871
|
|
|
|
|
|
|
A hashref of student ids and final grades. |
1872
|
|
|
|
|
|
|
|
1873
|
|
|
|
|
|
|
=cut |
1874
|
|
|
|
|
|
|
|
1875
|
|
|
|
|
|
|
method grades { |
1876
|
|
|
|
|
|
|
my $league = $self->league; |
1877
|
|
|
|
|
|
|
my $members = $league->members; |
1878
|
|
|
|
|
|
|
my $homework = $self->homeworkPercent; |
1879
|
|
|
|
|
|
|
my $classcomponent = $league->approach; |
1880
|
|
|
|
|
|
|
my $classwork = $self->classworkPercent; |
1881
|
|
|
|
|
|
|
my $exams = $self->examPercent; |
1882
|
|
|
|
|
|
|
my @ids = map { $_->{id} } @$members; |
1883
|
|
|
|
|
|
|
my $weights = $self->weights; |
1884
|
|
|
|
|
|
|
my %grades = map { $_ => $self->sprintround( |
1885
|
|
|
|
|
|
|
$classwork->{$_} * $weights->{classwork} /100 + |
1886
|
|
|
|
|
|
|
$homework->{$_} * $weights->{homework} /100 + |
1887
|
|
|
|
|
|
|
$exams->{$_} * $weights->{exams} /100 ) |
1888
|
|
|
|
|
|
|
} @ids; |
1889
|
|
|
|
|
|
|
\%grades; |
1890
|
|
|
|
|
|
|
} |
1891
|
|
|
|
|
|
|
|
1892
|
|
|
|
|
|
|
} |
1893
|
|
|
|
|
|
|
|
1894
|
|
|
|
|
|
|
no Moose; |
1895
|
|
|
|
|
|
|
|
1896
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
1897
|
|
|
|
|
|
|
|
1898
|
|
|
|
|
|
|
1; # End of Grades |
1899
|
|
|
|
|
|
|
|
1900
|
|
|
|
|
|
|
=head1 AUTHOR |
1901
|
|
|
|
|
|
|
|
1902
|
|
|
|
|
|
|
Dr Bean, C<< <drbean, followed by the at mark (@), cpan, then a dot, and finally, org> >> |
1903
|
|
|
|
|
|
|
|
1904
|
|
|
|
|
|
|
=head1 BUGS |
1905
|
|
|
|
|
|
|
|
1906
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
1907
|
|
|
|
|
|
|
C<bug-grades at rt.cpan.org>, or through the web interface at |
1908
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Grades>. |
1909
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on |
1910
|
|
|
|
|
|
|
your bug as I make changes. |
1911
|
|
|
|
|
|
|
|
1912
|
|
|
|
|
|
|
=head1 SUPPORT |
1913
|
|
|
|
|
|
|
|
1914
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1915
|
|
|
|
|
|
|
|
1916
|
|
|
|
|
|
|
perldoc Grades |
1917
|
|
|
|
|
|
|
|
1918
|
|
|
|
|
|
|
You can also look for information at: |
1919
|
|
|
|
|
|
|
|
1920
|
|
|
|
|
|
|
=over 4 |
1921
|
|
|
|
|
|
|
|
1922
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
1923
|
|
|
|
|
|
|
|
1924
|
|
|
|
|
|
|
L<http://annocpan.org/dist/Grades> |
1925
|
|
|
|
|
|
|
|
1926
|
|
|
|
|
|
|
=item * CPAN Ratings |
1927
|
|
|
|
|
|
|
|
1928
|
|
|
|
|
|
|
L<http://cpanratings.perl.org/d/Grades> |
1929
|
|
|
|
|
|
|
|
1930
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1931
|
|
|
|
|
|
|
|
1932
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Grades> |
1933
|
|
|
|
|
|
|
|
1934
|
|
|
|
|
|
|
=item * Search CPAN |
1935
|
|
|
|
|
|
|
|
1936
|
|
|
|
|
|
|
L<http://search.cpan.org/dist/Grades> |
1937
|
|
|
|
|
|
|
|
1938
|
|
|
|
|
|
|
=back |
1939
|
|
|
|
|
|
|
|
1940
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
1941
|
|
|
|
|
|
|
|
1942
|
|
|
|
|
|
|
Copyright 2009 Dr Bean, all rights reserved. |
1943
|
|
|
|
|
|
|
|
1944
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
1945
|
|
|
|
|
|
|
under the same terms as Perl itself. |
1946
|
|
|
|
|
|
|
|
1947
|
|
|
|
|
|
|
|
1948
|
|
|
|
|
|
|
=cut |
1949
|
|
|
|
|
|
|
|
1950
|
|
|
|
|
|
|
|
1951
|
|
|
|
|
|
|
# vim: set ts=8 sts=4 sw=4 noet: |
1952
|
|
|
|
|
|
|
__END__ |