[system] / trunk / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / Stats.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1766 - (view) (download) (as text)

1 : gage 1430 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : gage 1766 # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm,v 1.15 2003/12/28 21:02:49 gage Exp $
5 : sh002i 1663 #
6 :     # This program is free software; you can redistribute it and/or modify it under
7 :     # the terms of either: (a) the GNU General Public License as published by the
8 :     # Free Software Foundation; either version 2, or (at your option) any later
9 :     # version, or (b) the "Artistic License" which comes with this package.
10 :     #
11 :     # This program is distributed in the hope that it will be useful, but WITHOUT
12 :     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 :     # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14 :     # Artistic License for more details.
15 : gage 1430 ################################################################################
16 :    
17 :     package WeBWorK::ContentGenerator::Instructor::Stats;
18 :     use base qw(WeBWorK::ContentGenerator::Instructor);
19 :    
20 :     =head1 NAME
21 :    
22 : sh002i 1619 WeBWorK::ContentGenerator::Instructor::Stats - Display statistics by user or
23 :     problem set.
24 : gage 1430
25 :     =cut
26 :    
27 :     use strict;
28 :     use warnings;
29 :     use CGI qw();
30 :     use WeBWorK::Utils qw(readDirectory list2hash max);
31 :     use WeBWorK::DB::Record::Set;
32 :    
33 :    
34 :     sub initialize {
35 : gage 1432 my $self = shift;
36 :     # FIXME are there args here?
37 :     my $type = shift || '';
38 : gage 1430 my @components = @_;
39 :     my $r = $self->{r};
40 :     my $db = $self->{db};
41 :     my $ce = $self->{ce};
42 :     my $authz = $self->{authz};
43 :     my $user = $r->param('user');
44 :     my $setName = $_[0];
45 : gage 1667 #FIXME these don't appear to be used any where
46 :     # $setName = 0 unless defined($setName); #FIXME relay to index page for statistics
47 :     # my $setRecord = $db->getGlobalSet($setName); # checked
48 :     # # die "global set $setName not found." unless $setRecord;
49 :     #
50 :     # $self->{set} = $setRecord;
51 :     #####################################
52 : gage 1432 $self->{type} = $type;
53 :     if ($type eq 'student') {
54 :     $self->{studentName } = $components[0] || $user;
55 :    
56 :     } elsif ($type eq 'set') {
57 :     $self->{setName} = $components[0] || 0 ;
58 :     }
59 :    
60 :    
61 : gage 1430 }
62 :    
63 :     sub path {
64 :     my $self = shift;
65 :     my $args = $_[-1];
66 :     my $ce = $self->{ce};
67 :     my $root = $ce->{webworkURLs}->{root};
68 :     my $courseName = $ce->{courseName};
69 : gage 1432
70 : gage 1430 return $self->pathMacro($args,
71 : sh002i 1681 "Home" => "$root",
72 :     $courseName => "$root/$courseName",
73 :     'Instructor Tools' => "$root/$courseName/instructor",
74 :     'Statistics' =>
75 :     ($self->{type}
76 :     ? "$root/$courseName/instructor/stats/"
77 :     : ""
78 :     ),
79 :     ($self->{type} eq 'set'
80 :     ? ("set ".$self->{setName} => '')
81 :     : ()
82 :     ),
83 :     ($self->{type} eq 'student'
84 :     ? ("user ".$self->{studentName} => '')
85 :     : ()
86 :     ),
87 : gage 1430 );
88 :     }
89 :    
90 : gage 1432 sub title {
91 : gage 1430 my ($self, @components) = @_;
92 : gage 1432 my $type = $self->{type};
93 :     my $string = "Statistics for ".$self->{ce}->{courseName}." ";
94 :     if ($type eq 'student') {
95 :     $string .= "student ".$self->{studentName};
96 :     } elsif ($type eq 'set' ) {
97 :     $string .= "set ".$self->{setName};
98 :     }
99 :     return $string;
100 : gage 1430 }
101 : gage 1432 sub body {
102 :     my $self = shift;
103 :     my $args = pop(@_);
104 :     my $type = $self->{type};
105 :     if ($type eq 'student') {
106 :     $self->displayStudents($self->{studentName});
107 :     } elsif( $type eq 'set') {
108 :     my $setName = $self->{setName};
109 :     $self->displaySets($self->{setName});
110 :     } elsif ($type eq '') {
111 :     $self->index;
112 :     } else {
113 :     warn "Don't recognize statistics display type: |$type|";
114 : gage 1430
115 : gage 1432 }
116 :    
117 :    
118 :     return '';
119 :    
120 :     }
121 :     sub index {
122 :     my $self = shift;
123 :     my $ce = $self->{ce};
124 :     my $r = $self->{r};
125 :     my $courseName = $ce->{courseName};
126 :     my $db = $self->{db};
127 :     my @studentList = sort $db->listUsers;
128 :     my @setList = sort $db->listGlobalSets;
129 :     my $uri = $r->uri;
130 : gage 1433 my @setLinks = ();
131 :     my @studentLinks = ();
132 : gage 1432 foreach my $set (@setList) {
133 : gage 1433 push @setLinks, CGI::a({-href=>"${uri}set/$set/?".$self->url_authen_args },"set $set" );
134 : gage 1432 }
135 : gage 1433
136 : gage 1432 foreach my $student (@studentList) {
137 : gage 1433 push @studentLinks, CGI::a({-href=>"${uri}student/$student/?".$self->url_authen_args}," $student" ),;
138 : gage 1432 }
139 : gage 1433 print join("",
140 : gage 1434 CGI::start_table({-border=>2, -cellpadding=>20}),
141 : gage 1433 CGI::Tr(
142 :     CGI::td({-valign=>'top'},
143 :     CGI::h3({-align=>'center'},'View statistics by set'),
144 :     CGI::ul( CGI::li( [@setLinks] ) ),
145 :     ),
146 :     CGI::td({-valign=>'top'},
147 :     CGI::h3({-align=>'center'},'View statistics by student'),
148 :     CGI::ul(CGI::li( [ @studentLinks ] ) ),
149 :     ),
150 :     ),
151 :     CGI::end_table(),
152 :     );
153 : gage 1432
154 :     }
155 :     sub displaySets {
156 :     my $self = shift;
157 : gage 1430 #FIXME
158 : gage 1432 my $setName = shift;
159 : gage 1430
160 : gage 1590 my $r = $self->{r};
161 :     my $db = $self->{db};
162 :     my $ce = $self->{ce};
163 :     my $authz = $self->{authz};
164 :     my $user = $r->param('user');
165 : gage 1430 my $courseName = $ce->{courseName};
166 : gage 1667 my $setRecord = $db->getGlobalSet($setName); # checked
167 :     die "global set $setName not found." unless $setRecord;
168 : gage 1590 my $root = $ce->{webworkURLs}->{root};
169 :     my $url = $r->uri;
170 :     my $sort_method_name = $r->param('sort');
171 : gage 1430 my @studentList = $db->listUsers;
172 : gage 1432
173 : gage 1590 my $sort_method = sub {
174 :     my ($a,$b) = @_;
175 :     return 0 unless defined($sort_method_name);
176 :     return $b->{score} <=> $a->{score} if $sort_method_name eq 'score';
177 :     return $b->{index} <=> $a->{index} if $sort_method_name eq 'index';
178 :     return $a->{section} cmp $b->{section} if $sort_method_name eq 'section';
179 :     if ($sort_method_name =~/p(\d+)/) {
180 : gage 1766 my $left = $b->{problemData}->{$1} ||0;
181 :     my $right = $a->{problemData}->{$1} ||0;
182 :     return $left <=> $right; # sort by number of attempts.
183 : gage 1590 }
184 : gage 1432
185 : gage 1590 };
186 :     #FIXME need to be able to sort by index and score as well.
187 : gage 1430 ###############################################################
188 :     # Print table
189 :     ###############################################################
190 :     my @problems = sort {$a <=> $b } $db->listUserProblems($user, $setName);
191 : gage 1590
192 : gage 1430 # FIXME I'm assuming the problems are all the same
193 :    
194 :     my $num_of_problems = @problems;
195 : gage 1487 # get user records
196 : gage 1632 $WeBWorK::timer->continue("Begin obtaining user records for set $setName") if defined($WeBWorK::timer);
197 : gage 1590 my @userRecords = $db->getUsers(@studentList);
198 : gage 1632 $WeBWorK::timer->continue("End obtaining user records for set $setName") if defined($WeBWorK::timer);
199 : gage 1766 $WeBWorK::timer->continue("begin main loop") if defined($WeBWorK::timer);
200 : gage 1590 my @augmentedUserRecords = ();
201 : gage 1487 foreach my $studentRecord (@userRecords) {
202 : gage 1691 next unless ref($studentRecord);
203 : gage 1487 my $student = $studentRecord->user_id;
204 : gage 1430 next if $studentRecord->last_name =~/^practice/i; # don't show practice users
205 :     next if $studentRecord->status !~/C/; # don't show dropped students FIXME
206 :     my $status = 0;
207 :     my $attempted = 0;
208 :     my $longStatus = '';
209 :     my $string = '';
210 :     my $twoString = '';
211 :     my $totalRight = 0;
212 :     my $total = 0;
213 :     my $num_of_attempts = 0;
214 : gage 1590 my %h_problemData = ();
215 :     my $probNum = 0;
216 : gage 1632 my @triplets = map {[$student, $setName, $_ ]} @problems;
217 :     $WeBWorK::timer->continue("Begin obtaining problem records for user $student set $setName") if defined($WeBWorK::timer);
218 : gage 1766 #my @problemRecords = $db->getUserProblems( @triplets );
219 :     my @problemRecords = $db->getAllUserProblems( $student, $setName );
220 : gage 1632 $WeBWorK::timer->continue("End obtaining problem records for user $student set $setName") if defined($WeBWorK::timer);
221 :    
222 :     foreach my $problemRecord (@problemRecords) {
223 : gage 1691 next unless ref($problemRecord);
224 : gage 1632 my $prob = $problemRecord->problem_id;
225 :     #foreach my $prob (@problems) {
226 :     #my $problemRecord = $db->getUserProblem($student, $setName, $prob);
227 : gage 1590 $probNum++;
228 : gage 1430 my $valid_status = 0;
229 :     unless (defined($problemRecord) ){
230 :     # warn "Can't find record for problem $prob in set $setName for $student";
231 :     # FIXME check the legitimate reasons why a student record might not be defined
232 :     next;
233 :     }
234 :     $status = $problemRecord->status || 0;
235 :     $attempted = $problemRecord->attempted;
236 :     if (!$attempted){
237 :     $longStatus = '. ';
238 :     }
239 :     elsif ($status >= 0 and $status <=1 ) {
240 :     $valid_status = 1;
241 :     $longStatus = int(100*$status+.5);
242 :     if ($longStatus == 100) {
243 :     $longStatus = 'C ';
244 :     }
245 :     else {
246 :     $longStatus = &threeSpaceFill($longStatus);
247 :     }
248 :     }
249 :     else {
250 :     $longStatus = 'X ';
251 :     }
252 :    
253 : gage 1590 my $incorrect = $problemRecord->num_incorrect || 0;
254 :     # It's possible that $incorrect is an empty or blank string instead of 0 the || clause fixes this and prevents
255 :     # warning messages in the comparison below.
256 : gage 1430 $incorrect = ($incorrect < 99) ? $incorrect: 99; # take min
257 :     $string .= $longStatus;
258 :     $twoString .= threeSpaceFill($incorrect);
259 :     my $probValue = $problemRecord->value;
260 :     $probValue = 1 unless defined($probValue); # FIXME?? set defaults here?
261 :     $total += $probValue;
262 :     $totalRight += round_score($status*$probValue) if $valid_status;
263 :     my $num_correct = $problemRecord->num_incorrect || 0;
264 :     my $num_incorrect = $problemRecord->num_correct || 0;
265 :     $num_of_attempts += $num_correct + $num_incorrect;
266 : gage 1590 $h_problemData{$probNum} = $incorrect;
267 : gage 1430 }
268 :     # FIXME we can do this more effficiently get the list first
269 :    
270 : gage 1590
271 : gage 1487 my $act_as_student_url = "$root/$courseName/$setName?user=".$r->param("user").
272 :     "&effectiveUser=".$studentRecord->user_id()."&key=".$r->param("key");
273 : gage 1430 my $email = $studentRecord->email_address;
274 :     # FIXME this needs formatting
275 :    
276 :     my $avg_num_attempts = ($num_of_problems) ? $num_of_attempts/$num_of_problems : 0;
277 :     my $successIndicator = ($avg_num_attempts) ? ($totalRight/$total)**2/$avg_num_attempts : 0 ;
278 : gage 1590 my $temp_hash = { user_id => $studentRecord->user_id,
279 :     last_name => $studentRecord->last_name,
280 :     first_name => $studentRecord->first_name,
281 :     score => $totalRight,
282 :     total => $total,
283 :     index => $successIndicator,
284 :     section => $studentRecord->section,
285 :     recitation => $studentRecord->recitation,
286 :     problemString => "<pre>$string\n$twoString</pre>",
287 :     act_as_student => $act_as_student_url,
288 :     email_address => $studentRecord->email_address,
289 :     problemData => {%h_problemData},
290 :     };
291 :     push( @augmentedUserRecords, $temp_hash );
292 :    
293 :     }
294 : gage 1766 $WeBWorK::timer->continue("end mainloop") if defined($WeBWorK::timer);
295 : gage 1430
296 : gage 1590 @augmentedUserRecords = sort { &$sort_method($a,$b)
297 :     ||
298 :     lc($a->{last_name}) cmp lc($b->{last_name} ) } @augmentedUserRecords;
299 :    
300 :     # construct header
301 :     my $problem_header = '';
302 :     my $i=0;
303 :     foreach (@problems) {
304 :     $i++;
305 :     $problem_header .= CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=p$i"},threeSpaceFill($i) );
306 :     }
307 :     print
308 :     defined($sort_method_name) ?"sort method is $sort_method_name":"",
309 : gage 1766 CGI::start_table({-border=>5,style=>'font-size:smaller'}),
310 : gage 1590 CGI::Tr(CGI::th( {-align=>'center'},
311 :     [CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=name"},'Name'),
312 :     CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=score"},'Score'),
313 :     'Out'.CGI::br().'Of',
314 :     CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=index"},'Ind'),
315 :     '<pre>Problems'.CGI::br().$problem_header.'</pre>',
316 :     CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=section"},'Section'),
317 :     'Recitation',
318 :     'login_name',
319 :     ])
320 :    
321 :     );
322 :    
323 :     foreach my $rec (@augmentedUserRecords) {
324 :     my $fullName = join("", $rec->{first_name}," ", $rec->{last_name});
325 :     my $email = $rec->{email_address};
326 :     my $twoString = $rec->{twoString};
327 : gage 1430 print CGI::Tr(
328 : gage 1590 CGI::td(CGI::a({-href=>$rec->{act_as_student}},$fullName), CGI::br(), CGI::a({-href=>"mailto:$email"},$email)),
329 :     CGI::td( sprintf("%0.2f",$rec->{score}) ), # score
330 :     CGI::td($rec->{total}), # out of
331 :     CGI::td(sprintf("%0.0f",100*($rec->{index}) )), # indicator
332 :     CGI::td($rec->{problemString}), # problems
333 :     CGI::td($rec->{section}),
334 :     CGI::td($rec->{recitation}),
335 :     CGI::td($rec->{user_id}),
336 : gage 1430
337 :     );
338 :     }
339 : gage 1590
340 : gage 1430 print CGI::end_table();
341 :    
342 :    
343 :    
344 :    
345 :     return "";
346 :     }
347 : gage 1432 sub displayStudents {
348 :     my $self = shift;
349 :     my $studentName = shift;
350 :     my $r = $self->{r};
351 :     my $db = $self->{db};
352 :     my $ce = $self->{ce};
353 :     my $courseName = $ce->{courseName};
354 : gage 1667 my $studentRecord = $db->getUser($studentName); # checked
355 :     die "record for user $studentName not found" unless $studentRecord;
356 : gage 1497 my $root = $ce->{webworkURLs}->{root};
357 : gage 1432
358 :     my @setIDs = sort $db->listUserSets($studentName);
359 :     my $fullName = join("", $studentRecord->first_name," ", $studentRecord->last_name);
360 : gage 1497 my $act_as_student_url = "$root/$courseName/?user=".$r->param("user").
361 :     "&effectiveUser=".$studentRecord->user_id()."&key=".$r->param("key");
362 :    
363 : gage 1432 my $email = $studentRecord->email_address;
364 : gage 1497 print CGI::h3($fullName ),
365 :     CGI::a({-href=>"mailto:$email"},$email),CGI::br(),
366 :     "Section: ", $studentRecord->section, CGI::br(),
367 :     "Recitation: ", $studentRecord->recitation,CGI::br(),
368 :     CGI::a({-href=>$act_as_student_url},$studentRecord->user_id);
369 : sh002i 1619
370 :     ###############################################################
371 :     # Print table
372 :     ###############################################################
373 : gage 1430
374 : gage 1432 # FIXME I'm assuming the problems are all the same
375 : sh002i 1619 # FIXME what does this mean?
376 : gage 1432
377 : sh002i 1619 my @rows;
378 : gage 1632 my $max_problems=0;
379 : sh002i 1619
380 : gage 1432 foreach my $setName (@setIDs) {
381 :     my $status = 0;
382 :     my $attempted = 0;
383 :     my $longStatus = '';
384 :     my $string = '';
385 :     my $twoString = '';
386 :     my $totalRight = 0;
387 :     my $total = 0;
388 :     my $num_of_attempts = 0;
389 :     my @problems = sort {$a <=> $b } $db->listUserProblems($studentName, $setName);
390 :     my $num_of_problems = @problems;
391 : sh002i 1619 $max_problems = $num_of_problems if $num_of_problems > $max_problems;
392 : gage 1432 # construct header
393 : gage 1632 $WeBWorK::timer->continue("Begin collecting problems for set $setName") if defined($WeBWorK::timer);
394 :     my @problemRecords = $db->getUserProblems( map {[$studentName, $setName,$_]} @problems);
395 :     $WeBWorK::timer->continue("End collecting problems for set $setName") if defined($WeBWorK::timer);
396 :     foreach my $problemRecord (@problemRecords) {
397 :     my $prob = $problemRecord->problem_id;
398 :     #foreach my $prob (@problems) {
399 :     #my $problemRecord = $db->getUserProblem($studentName, $setName, $prob);
400 : gage 1432
401 :     my $valid_status = 0;
402 :     unless (defined($problemRecord) ){
403 :     # warn "Can't find record for problem $prob in set $setName for $student";
404 :     # FIXME check the legitimate reasons why a student record might not be defined
405 :     next;
406 :     }
407 :     $status = $problemRecord->status || 0;
408 :     $attempted = $problemRecord->attempted;
409 :     if (!$attempted){
410 :     $longStatus = '. ';
411 :     }
412 :     elsif ($status >= 0 and $status <=1 ) {
413 :     $valid_status = 1;
414 :     $longStatus = int(100*$status+.5);
415 :     if ($longStatus == 100) {
416 :     $longStatus = 'C ';
417 :     }
418 :     else {
419 :     $longStatus = &threeSpaceFill($longStatus);
420 :     }
421 :     }
422 :     else {
423 :     $longStatus = 'X ';
424 :     }
425 :    
426 :     my $incorrect = $problemRecord->num_incorrect;
427 :     $incorrect = ($incorrect < 99) ? $incorrect: 99; # take min
428 :     $string .= $longStatus;
429 :     $twoString .= threeSpaceFill($incorrect);
430 :     my $probValue = $problemRecord->value;
431 :     $probValue = 1 unless defined($probValue); # FIXME?? set defaults here?
432 :     $total += $probValue;
433 :     $totalRight += round_score($status*$probValue) if $valid_status;
434 :     my $num_correct = $problemRecord->num_incorrect || 0;
435 :     my $num_incorrect = $problemRecord->num_correct || 0;
436 :     $num_of_attempts += $num_correct + $num_incorrect;
437 :     }
438 : gage 1632
439 : gage 1432 # FIXME we can do this more effficiently get the list first
440 :    
441 :    
442 :     # FIXME this needs formatting
443 :    
444 :     my $avg_num_attempts = ($num_of_problems) ? $num_of_attempts/$num_of_problems : 0;
445 :     my $successIndicator = ($avg_num_attempts) ? ($totalRight/$total)**2/$avg_num_attempts : 0 ;
446 :    
447 : sh002i 1619 push @rows, CGI::Tr(
448 : gage 1432 CGI::td($setName),
449 : gage 1497 CGI::td(sprintf("%0.2f",$totalRight)), # score
450 : gage 1432 CGI::td($total), # out of
451 :     CGI::td(sprintf("%0.0f",100*$successIndicator)), # indicator
452 :     CGI::td("<pre>$string\n$twoString</pre>"), # problems
453 :     #CGI::td($studentRecord->section),
454 :     #CGI::td($studentRecord->recitation),
455 :     #CGI::td($studentRecord->user_id),
456 :    
457 :     );
458 :    
459 :     }
460 : sh002i 1619
461 :     my $problem_header = "";
462 :     foreach (1 .. $max_problems) {
463 :     $problem_header .= &threeSpaceFill($_);
464 :     }
465 :    
466 :     my $table_header = join("\n",
467 :     CGI::start_table({-border=>5}),
468 :     CGI::Tr(
469 :     CGI::th({ -align=>'center',},'Set'),
470 :     CGI::th({ -align=>'center', },'Score'),
471 :     CGI::th({ -align=>'center', },'Out'.CGI::br().'Of'),
472 :     CGI::th({ -align=>'center', },'Ind'),
473 :     CGI::th({ -align=>'center', },'Problems'.CGI::br().CGI::pre($problem_header)),
474 :     #CGI::th({ -align=>'center', },'Section'),
475 :     #CGI::th({ -align=>'center', },'Recitation'),
476 :     #CGI::th({ -align=>'center', },'login_name'),
477 :     #CGI::th({ -align=>'center', },'ID'),
478 :     )
479 :     );
480 :    
481 :     print $table_header;
482 :     print @rows;
483 : gage 1432 print CGI::end_table();
484 :    
485 :     return "";
486 :     }
487 :    
488 : gage 1430 #################################
489 :     # Utility function NOT a method
490 :     #################################
491 :     sub threeSpaceFill {
492 : gage 1436 my $num = shift @_ || 0;
493 :    
494 : gage 1430 if ($num < 10) {return "$num".' ';}
495 :     elsif ($num < 100) {return "$num".' ';}
496 :     else {return "$num";}
497 :     }
498 :     sub round_score{
499 :     return shift;
500 :     }
501 :     1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9