| 1 | ################################################################################ |
1 | ################################################################################ |
| 2 | # WeBWorK Online Homework Delivery System |
2 | # WeBWorK Online Homework Delivery System |
| 3 | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ |
3 | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ |
| 4 | # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Grades.pm,v 1.1 2004/03/06 18:50:31 gage Exp $ |
4 | # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Grades.pm,v 1.2 2004/03/07 02:29:25 gage Exp $ |
| 5 | # |
5 | # |
| 6 | # This program is free software; you can redistribute it and/or modify it under |
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 |
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 |
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. |
9 | # version, or (b) the "Artistic License" which comes with this package. |
| … | |
… | |
| 30 | use WeBWorK::Utils qw(readDirectory list2hash max); |
30 | use WeBWorK::Utils qw(readDirectory list2hash max); |
| 31 | use WeBWorK::DB::Record::Set; |
31 | use WeBWorK::DB::Record::Set; |
| 32 | |
32 | |
| 33 | |
33 | |
| 34 | sub initialize { |
34 | sub initialize { |
| 35 | my $self = shift; |
35 | my ($self) = @_; |
| 36 | # FIXME are there args here? |
|
|
| 37 | my @components = @_; |
|
|
| 38 | my $r = $self->{r}; |
36 | my $r = $self->r; |
| 39 | my $type = $r->urlpath->arg("statType") || ''; |
|
|
| 40 | my $db = $self->{db}; |
37 | my $db = $r->db; |
| 41 | my $ce = $self->{ce}; |
38 | my $ce = $r->ce; |
| 42 | my $authz = $self->{authz}; |
39 | my $authz = $r->authz; |
|
|
40 | |
| 43 | my $userName = $r->param('user'); |
41 | my $userName = $r->param('user'); |
| 44 | my $effectiveUserName = defined($self->{r}->param("effectiveUser") ) ? $self->{r}->param("effectiveUser") : $userName; |
42 | my $effectiveUserName = defined($r->param("effectiveUser") ) ? $r->param("effectiveUser") : $userName; |
| 45 | $self->{userName} = $userName; |
43 | $self->{userName} = $userName; |
| 46 | $self->{studentName} = $effectiveUserName; |
44 | $self->{studentName} = $effectiveUserName; |
| 47 | } |
45 | } |
| 48 | |
46 | |
| 49 | sub path { |
|
|
| 50 | my $self = shift; |
|
|
| 51 | my $args = $_[-1]; |
|
|
| 52 | my $ce = $self->{ce}; |
|
|
| 53 | my $root = $ce->{webworkURLs}->{root}; |
|
|
| 54 | my $courseName = $ce->{courseName}; |
|
|
| 55 | |
|
|
| 56 | return $self->pathMacro($args, |
|
|
| 57 | "Home" => "$root", |
|
|
| 58 | $courseName => "$root/$courseName", |
|
|
| 59 | 'Grades' => '', |
|
|
| 60 | |
|
|
| 61 | ); |
|
|
| 62 | } |
|
|
| 63 | |
|
|
| 64 | sub title { |
|
|
| 65 | my $self = shift; |
|
|
| 66 | my $string = "Grades for ".$self->{studentName}." in course ". $self->{ce}->{courseName}." "; |
|
|
| 67 | return $string; |
|
|
| 68 | } |
|
|
| 69 | sub body { |
47 | sub body { |
| 70 | my $self = shift; |
48 | my ($self) = @_; |
| 71 | my $args = pop(@_); |
49 | |
| 72 | my $type = $self->{type}; |
|
|
| 73 | |
|
|
| 74 | $self->displayStudentStats($self->{studentName}); |
50 | $self->displayStudentStats($self->{studentName}); |
| 75 | |
51 | |
| 76 | print $self->scoring_info(); |
52 | print $self->scoring_info(); |
| 77 | |
53 | |
| 78 | return ''; |
54 | return ''; |
| 79 | |
55 | |
| 80 | } |
56 | } |
| 81 | |
|
|
| 82 | |
57 | |
| 83 | ############################################ |
58 | ############################################ |
| 84 | # Borrowed from SendMail.pm and Instructor.pm |
59 | # Borrowed from SendMail.pm and Instructor.pm |
| 85 | ############################################ |
60 | ############################################ |
|
|
61 | |
| 86 | sub getRecord { |
62 | sub getRecord { |
| 87 | my $self = shift; |
63 | my $self = shift; |
| 88 | my $line = shift; |
64 | my $line = shift; |
| 89 | my $delimiter = shift; |
65 | my $delimiter = shift; |
| 90 | $delimiter = ',' unless defined($delimiter); |
66 | $delimiter = ',' unless defined($delimiter); |
| … | |
… | |
| 102 | $lineArray[0] =~s/^\s*//; # remove white space from first element |
78 | $lineArray[0] =~s/^\s*//; # remove white space from first element |
| 103 | @lineArray; |
79 | @lineArray; |
| 104 | } |
80 | } |
| 105 | |
81 | |
| 106 | sub read_scoring_file { # used in SendMail and Grades?....? |
82 | sub read_scoring_file { # used in SendMail and Grades?....? |
| 107 | my $self = shift; |
83 | my ($self, $fileName, $delimiter) = @_; |
| 108 | my $fileName = shift; |
84 | my $r = $self->r; |
| 109 | my $delimiter = shift; |
85 | my $ce = $r->ce; |
|
|
86 | |
| 110 | $delimiter = ',' unless defined($delimiter); |
87 | $delimiter = ',' unless defined($delimiter); |
| 111 | my $scoringDirectory= $self->{ce}->{courseDirs}->{scoring}; |
88 | my $scoringDirectory= $ce->{courseDirs}->{scoring}; |
| 112 | my $filePath = "$scoringDirectory/$fileName"; |
89 | my $filePath = "$scoringDirectory/$fileName"; |
| 113 | # Takes a delimited file as a parameter and returns an |
90 | # Takes a delimited file as a parameter and returns an |
| 114 | # associative array with the first field as the key. |
91 | # associative array with the first field as the key. |
| 115 | # Blank lines are skipped. White space is removed |
92 | # Blank lines are skipped. White space is removed |
| 116 | my(@dbArray,$key,$dbString); |
93 | my(@dbArray,$key,$dbString); |
| … | |
… | |
| 132 | } else { |
109 | } else { |
| 133 | warn "Couldn't read file $filePath"; |
110 | warn "Couldn't read file $filePath"; |
| 134 | } |
111 | } |
| 135 | return \%assocArray; |
112 | return \%assocArray; |
| 136 | } |
113 | } |
|
|
114 | |
| 137 | sub submission_error { |
115 | sub submission_error { |
| 138 | my $self = shift; |
116 | my $self = shift; |
| 139 | my $msg = join( " ", @_); |
117 | my $msg = join( " ", @_); |
| 140 | $self->{submitError} .= CGI::br().$msg; |
118 | $self->{submitError} .= CGI::br().$msg; |
| 141 | return; |
119 | return; |
| 142 | } |
120 | } |
|
|
121 | |
| 143 | sub scoring_info { |
122 | sub scoring_info { |
| 144 | my $self = shift; |
123 | my ($self) = @_; |
|
|
124 | my $r = $self->r; |
|
|
125 | my $db = $r->db; |
|
|
126 | my $ce = $r->ce; |
|
|
127 | |
| 145 | my $userName = $self->{r}->param('effectiveUser') || $self->{r}->param('user'); |
128 | my $userName = $r->param('effectiveUser') || $r->param('user'); |
| 146 | my $ur = $self->{db}->getUser($userName); |
129 | my $ur = $db->getUser($userName); |
| 147 | my $emailDirectory = $self->{ce}->{courseDirs}->{email}; |
130 | my $emailDirectory = $ce->{courseDirs}->{email}; |
| 148 | my $filePath = "$emailDirectory/report_grades.msg"; |
131 | my $filePath = "$emailDirectory/report_grades.msg"; |
| 149 | my $merge_file = "report_grades_data.csv"; |
132 | my $merge_file = "report_grades_data.csv"; |
| 150 | my $delimiter = ','; |
133 | my $delimiter = ','; |
| 151 | my $rh_merge_data = $self->read_scoring_file("$merge_file", "$delimiter"); |
134 | my $rh_merge_data = $self->read_scoring_file("$merge_file", "$delimiter"); |
| 152 | my $text; |
135 | my $text; |
| … | |
… | |
| 194 | {style =>"background-color:#DDDDDD"}, "More scoring information goes here in \$emailDirectory/report_grades.msg. It |
177 | {style =>"background-color:#DDDDDD"}, "More scoring information goes here in \$emailDirectory/report_grades.msg. It |
| 195 | is merged with the file \$scoringDirectory/report_grades_data.csv. <p> |
178 | is merged with the file \$scoringDirectory/report_grades_data.csv. <p> |
| 196 | <pre>$msg</pre>" |
179 | <pre>$msg</pre>" |
| 197 | ); |
180 | ); |
| 198 | } |
181 | } |
| 199 | ############################################################################## |
182 | |
| 200 | # sub index { |
|
|
| 201 | # my $self = shift; |
|
|
| 202 | # my $ce = $self->{ce}; |
|
|
| 203 | # my $r = $self->{r}; |
|
|
| 204 | # my $courseName = $ce->{courseName}; |
|
|
| 205 | # my $db = $self->{db}; |
|
|
| 206 | # my @studentList = sort $db->listUsers; |
|
|
| 207 | # my @setList = sort $db->listGlobalSets; |
|
|
| 208 | # my $uri = $r->uri; |
|
|
| 209 | # my @setLinks = (); |
|
|
| 210 | # my @studentLinks = (); |
|
|
| 211 | # foreach my $set (@setList) { |
|
|
| 212 | # push @setLinks, CGI::a({-href=>"${uri}set/$set/?".$self->url_authen_args },"set $set" ); |
|
|
| 213 | # } |
|
|
| 214 | # |
|
|
| 215 | # foreach my $student (@studentList) { |
|
|
| 216 | # push @studentLinks, CGI::a({-href=>"${uri}student/$student/?".$self->url_authen_args}," $student" ),; |
|
|
| 217 | # } |
|
|
| 218 | # print join("", |
|
|
| 219 | # CGI::start_table({-border=>2, -cellpadding=>20}), |
|
|
| 220 | # CGI::Tr( |
|
|
| 221 | # CGI::td({-valign=>'top'}, |
|
|
| 222 | # CGI::h3({-align=>'center'},'View statistics by set'), |
|
|
| 223 | # CGI::ul( CGI::li( [@setLinks] ) ), |
|
|
| 224 | # ), |
|
|
| 225 | # CGI::td({-valign=>'top'}, |
|
|
| 226 | # CGI::h3({-align=>'center'},'View statistics by student'), |
|
|
| 227 | # CGI::ul(CGI::li( [ @studentLinks ] ) ), |
|
|
| 228 | # ), |
|
|
| 229 | # ), |
|
|
| 230 | # CGI::end_table(), |
|
|
| 231 | # ); |
|
|
| 232 | # |
|
|
| 233 | # } |
|
|
| 234 | ################################################### |
|
|
| 235 | # Determines the percentage of students whose score is greater than a given value |
|
|
| 236 | # The percentages are fixed at 75, 50, 25 and 5% |
|
|
| 237 | # sub determine_percentiles { |
|
|
| 238 | # my $percent_brackets = shift; |
|
|
| 239 | # my @list_of_scores = @_; |
|
|
| 240 | # @list_of_scores = sort {$a<=>$b} @list_of_scores; |
|
|
| 241 | # my %percentiles = (); |
|
|
| 242 | # my $num_students = $#list_of_scores; |
|
|
| 243 | # foreach my $percentage (@{$percent_brackets}) { |
|
|
| 244 | # $percentiles{$percentage} = @list_of_scores[int( (100-$percentage)*$num_students/100)]; |
|
|
| 245 | # } |
|
|
| 246 | # # for example |
|
|
| 247 | # # $percentiles{75} = @list_of_scores[int( 25*$num_students/100)]; |
|
|
| 248 | # # means that 75% of the students received this score ($percentiles{75}) or higher |
|
|
| 249 | # %percentiles; |
|
|
| 250 | # } |
|
|
| 251 | # sub displaySets { |
|
|
| 252 | # my $self = shift; |
|
|
| 253 | # my $setName = shift; |
|
|
| 254 | # my $r = $self->{r}; |
|
|
| 255 | # my $db = $self->{db}; |
|
|
| 256 | # my $ce = $self->{ce}; |
|
|
| 257 | # my $authz = $self->{authz}; |
|
|
| 258 | # my $user = $r->param('user'); |
|
|
| 259 | # my $courseName = $ce->{courseName}; |
|
|
| 260 | # my $setRecord = $self->{setRecord}; |
|
|
| 261 | # my $root = $ce->{webworkURLs}->{root}; |
|
|
| 262 | # my $url = $r->uri; |
|
|
| 263 | # my $sort_method_name = $r->param('sort'); |
|
|
| 264 | # my @studentList = $db->listUsers; |
|
|
| 265 | # |
|
|
| 266 | # my @index_list = (); # list of all student index |
|
|
| 267 | # my @score_list = (); # list of all student total percentage scores |
|
|
| 268 | # my %attempts_list_for_problem = (); # a list of the number of attempts for each problem |
|
|
| 269 | # my %number_ofstudents_attempting_problem = (); # the number of students attempting this problem. |
|
|
| 270 | # my %correct_answers_for_problem = (); # the number of students correctly answering this problem (partial correctness allowed) |
|
|
| 271 | # my $sort_method = sub { |
|
|
| 272 | # my ($a,$b) = @_; |
|
|
| 273 | # return 0 unless defined($sort_method_name); |
|
|
| 274 | # return $b->{score} <=> $a->{score} if $sort_method_name eq 'score'; |
|
|
| 275 | # return $b->{index} <=> $a->{index} if $sort_method_name eq 'index'; |
|
|
| 276 | # return $a->{section} cmp $b->{section} if $sort_method_name eq 'section'; |
|
|
| 277 | # if ($sort_method_name =~/p(\d+)/) { |
|
|
| 278 | # my $left = $b->{problemData}->{$1} ||0; |
|
|
| 279 | # my $right = $a->{problemData}->{$1} ||0; |
|
|
| 280 | # return $left <=> $right; # sort by number of attempts. |
|
|
| 281 | # } |
|
|
| 282 | # |
|
|
| 283 | # }; |
|
|
| 284 | # |
|
|
| 285 | # ############################################################### |
|
|
| 286 | # # Print tables |
|
|
| 287 | # ############################################################### |
|
|
| 288 | # |
|
|
| 289 | # my $max_num_problems = 0; |
|
|
| 290 | # # get user records |
|
|
| 291 | # $WeBWorK::timer->continue("Begin obtaining user records for set $setName") if defined($WeBWorK::timer); |
|
|
| 292 | # my @userRecords = $db->getUsers(@studentList); |
|
|
| 293 | # $WeBWorK::timer->continue("End obtaining user records for set $setName") if defined($WeBWorK::timer); |
|
|
| 294 | # $WeBWorK::timer->continue("begin main loop") if defined($WeBWorK::timer); |
|
|
| 295 | # my @augmentedUserRecords = (); |
|
|
| 296 | # my $number_of_active_students; |
|
|
| 297 | # |
|
|
| 298 | # foreach my $studentRecord (@userRecords) { |
|
|
| 299 | # next unless ref($studentRecord); |
|
|
| 300 | # my $student = $studentRecord->user_id; |
|
|
| 301 | # next if $studentRecord->last_name =~/^practice/i; # don't show practice users |
|
|
| 302 | # next if $studentRecord->status !~/C/; # don't show dropped students FIXME |
|
|
| 303 | # $number_of_active_students++; |
|
|
| 304 | # my $status = 0; |
|
|
| 305 | # my $attempted = 0; |
|
|
| 306 | # my $longStatus = ''; |
|
|
| 307 | # my $string = ''; |
|
|
| 308 | # my $twoString = ''; |
|
|
| 309 | # my $totalRight = 0; |
|
|
| 310 | # my $total = 0; |
|
|
| 311 | # my $num_of_attempts = 0; |
|
|
| 312 | # my %h_problemData = (); |
|
|
| 313 | # my $probNum = 0; |
|
|
| 314 | # |
|
|
| 315 | # $WeBWorK::timer->continue("Begin obtaining problem records for user $student set $setName") if defined($WeBWorK::timer); |
|
|
| 316 | # |
|
|
| 317 | # my @problemRecords = sort {$a->problem_id <=> $b->problem_id } $db->getAllUserProblems( $student, $setName ); |
|
|
| 318 | # $WeBWorK::timer->continue("End obtaining problem records for user $student set $setName") if defined($WeBWorK::timer); |
|
|
| 319 | # my $num_of_problems = @problemRecords; |
|
|
| 320 | # my $max_num_problems = ($max_num_problems>= $num_of_problems) ? $max_num_problems : $num_of_problems; |
|
|
| 321 | # |
|
|
| 322 | # foreach my $problemRecord (@problemRecords) { |
|
|
| 323 | # next unless ref($problemRecord); |
|
|
| 324 | # my $probID = $problemRecord->problem_id; |
|
|
| 325 | # |
|
|
| 326 | # my $valid_status = 0; |
|
|
| 327 | # unless (defined($problemRecord) ){ |
|
|
| 328 | # # warn "Can't find record for problem $prob in set $setName for $student"; |
|
|
| 329 | # # FIXME check the legitimate reasons why a student record might not be defined |
|
|
| 330 | # next; |
|
|
| 331 | # } |
|
|
| 332 | # $status = $problemRecord->status || 0; |
|
|
| 333 | # $attempted = $problemRecord->attempted; |
|
|
| 334 | # if (!$attempted){ |
|
|
| 335 | # $longStatus = '. '; |
|
|
| 336 | # } |
|
|
| 337 | # elsif ($status >= 0 and $status <=1 ) { |
|
|
| 338 | # $valid_status = 1; |
|
|
| 339 | # $longStatus = int(100*$status+.5); |
|
|
| 340 | # if ($longStatus == 100) { |
|
|
| 341 | # $longStatus = 'C '; |
|
|
| 342 | # } |
|
|
| 343 | # else { |
|
|
| 344 | # $longStatus = &threeSpaceFill($longStatus); |
|
|
| 345 | # } |
|
|
| 346 | # } |
|
|
| 347 | # else { |
|
|
| 348 | # $longStatus = 'X '; |
|
|
| 349 | # } |
|
|
| 350 | # |
|
|
| 351 | # my $incorrect = $problemRecord->num_incorrect || 0; |
|
|
| 352 | # # It's possible that $incorrect is an empty or blank string instead of 0 the || clause fixes this and prevents |
|
|
| 353 | # # warning messages in the comparison below. |
|
|
| 354 | # $string .= $longStatus; |
|
|
| 355 | # $twoString .= threeSpaceFill($incorrect); |
|
|
| 356 | # my $probValue = $problemRecord->value; |
|
|
| 357 | # $probValue = 1 unless defined($probValue); # FIXME?? set defaults here? |
|
|
| 358 | # $total += $probValue; |
|
|
| 359 | # $totalRight += round_score($status*$probValue) if $valid_status; |
|
|
| 360 | # my $num_correct = $problemRecord->num_incorrect || 0; |
|
|
| 361 | # my $num_incorrect = $problemRecord->num_correct || 0; |
|
|
| 362 | # $num_of_attempts += $num_correct + $num_incorrect; |
|
|
| 363 | # |
|
|
| 364 | # $correct_answers_for_problem{$probID} = 0 unless defined($correct_answers_for_problem{$probID}); |
|
|
| 365 | # # add on the scores for this problem |
|
|
| 366 | # if (defined($attempted) and $attempted) { |
|
|
| 367 | # $number_ofstudents_attempting_problem{$probID}++; |
|
|
| 368 | # push( @{ $attempts_list_for_problem{$probID} } , $num_correct + $num_incorrect); |
|
|
| 369 | # $correct_answers_for_problem{$probID} += $status; |
|
|
| 370 | # } |
|
|
| 371 | # |
|
|
| 372 | # } |
|
|
| 373 | # |
|
|
| 374 | # |
|
|
| 375 | # my $act_as_student_url = "$root/$courseName/$setName?user=".$r->param("user"). |
|
|
| 376 | # "&effectiveUser=".$studentRecord->user_id()."&key=".$r->param("key"); |
|
|
| 377 | # my $email = $studentRecord->email_address; |
|
|
| 378 | # # FIXME this needs formatting |
|
|
| 379 | # |
|
|
| 380 | # my $avg_num_attempts = ($num_of_problems) ? $num_of_attempts/$num_of_problems : 0; |
|
|
| 381 | # my $successIndicator = ($avg_num_attempts) ? ($totalRight/$total)**2/$avg_num_attempts : 0 ; |
|
|
| 382 | # my $temp_hash = { user_id => $studentRecord->user_id, |
|
|
| 383 | # last_name => $studentRecord->last_name, |
|
|
| 384 | # first_name => $studentRecord->first_name, |
|
|
| 385 | # score => $totalRight, |
|
|
| 386 | # total => $total, |
|
|
| 387 | # index => $successIndicator, |
|
|
| 388 | # section => $studentRecord->section, |
|
|
| 389 | # recitation => $studentRecord->recitation, |
|
|
| 390 | # problemString => "<pre>$string\n$twoString</pre>", |
|
|
| 391 | # act_as_student => $act_as_student_url, |
|
|
| 392 | # email_address => $studentRecord->email_address, |
|
|
| 393 | # problemData => {%h_problemData}, |
|
|
| 394 | # }; |
|
|
| 395 | # # add this data to the list of total scores (out of 100) |
|
|
| 396 | # # add this data to the list of success indices. |
|
|
| 397 | # push( @index_list, $temp_hash->{index}); |
|
|
| 398 | # push( @score_list, ($temp_hash->{total}) ?$temp_hash->{score}/$temp_hash->{total} : 0 ) ; |
|
|
| 399 | # push( @augmentedUserRecords, $temp_hash ); |
|
|
| 400 | # |
|
|
| 401 | # } |
|
|
| 402 | # $WeBWorK::timer->continue("end mainloop") if defined($WeBWorK::timer); |
|
|
| 403 | # |
|
|
| 404 | # @augmentedUserRecords = sort { &$sort_method($a,$b) |
|
|
| 405 | # || |
|
|
| 406 | # lc($a->{last_name}) cmp lc($b->{last_name} ) } @augmentedUserRecords; |
|
|
| 407 | # |
|
|
| 408 | # # sort the problem IDs |
|
|
| 409 | # my @problemIDs = sort {$a<=>$b} keys %correct_answers_for_problem; |
|
|
| 410 | # # determine index quartiles |
|
|
| 411 | # my @brackets = (75, 50,25,5); |
|
|
| 412 | # my %index_percentiles = determine_percentiles(\@brackets, @index_list); |
|
|
| 413 | # my %score_percentiles = determine_percentiles(\@brackets, @score_list); |
|
|
| 414 | # my %attempts_percentiles_for_problem = (); |
|
|
| 415 | # foreach my $probID (@problemIDs) { |
|
|
| 416 | # $attempts_percentiles_for_problem{$probID} = { |
|
|
| 417 | # determine_percentiles([@brackets, 0], @{$attempts_list_for_problem{$probID}}) |
|
|
| 418 | # }; |
|
|
| 419 | # } |
|
|
| 420 | # |
|
|
| 421 | # ##################################################################################### |
|
|
| 422 | # # Table showing the percentage of students with correct answers for each problems |
|
|
| 423 | # ##################################################################################### |
|
|
| 424 | # print |
|
|
| 425 | # |
|
|
| 426 | # CGI::p('The percentage of active students with correct answers for each problem'), |
|
|
| 427 | # CGI::start_table({-border=>1}), |
|
|
| 428 | # CGI::Tr(CGI::td( |
|
|
| 429 | # ['Problem #', @problemIDs] |
|
|
| 430 | # )), |
|
|
| 431 | # CGI::Tr(CGI::td( |
|
|
| 432 | # [ '% correct',map { sprintf("%0.0f",100*$correct_answers_for_problem{$_}/$number_ofstudents_attempting_problem{$_}) } |
|
|
| 433 | # @problemIDs |
|
|
| 434 | # ] |
|
|
| 435 | # )), |
|
|
| 436 | # CGI::end_table(); |
|
|
| 437 | # |
|
|
| 438 | # ##################################################################################### |
|
|
| 439 | # # table showing percentile statistics for scores and success indices |
|
|
| 440 | # ##################################################################################### |
|
|
| 441 | # print |
|
|
| 442 | # |
|
|
| 443 | # CGI::p('The percentage of active students whose percentage scores and success indices are greater than the given values.'), |
|
|
| 444 | # CGI::start_table({-border=>1}), |
|
|
| 445 | # CGI::Tr( |
|
|
| 446 | # CGI::td( ['% students', |
|
|
| 447 | # (map { " $_" } @brackets) , |
|
|
| 448 | # 'top score ', |
|
|
| 449 | # ] |
|
|
| 450 | # ) |
|
|
| 451 | # ), |
|
|
| 452 | # CGI::Tr( |
|
|
| 453 | # CGI::td( [ |
|
|
| 454 | # 'Score', |
|
|
| 455 | # (map { '≥ '.sprintf("%0.0f",100*$score_percentiles{$_}) } @brackets), |
|
|
| 456 | # sprintf("%0.0f",100), |
|
|
| 457 | # ] |
|
|
| 458 | # ) |
|
|
| 459 | # ), |
|
|
| 460 | # CGI::Tr( |
|
|
| 461 | # CGI::td( [ |
|
|
| 462 | # 'Success Index', |
|
|
| 463 | # (map { '≥ '.sprintf("%0.0f",100*$index_percentiles{$_}) } @brackets), |
|
|
| 464 | # sprintf("%0.0f",100), |
|
|
| 465 | # ] |
|
|
| 466 | # ) |
|
|
| 467 | # ) |
|
|
| 468 | # ; |
|
|
| 469 | # |
|
|
| 470 | # print CGI::end_table(), |
|
|
| 471 | # |
|
|
| 472 | # ; |
|
|
| 473 | # |
|
|
| 474 | # ##################################################################################### |
|
|
| 475 | # # table showing percentile statistics for scores and success indices |
|
|
| 476 | # ##################################################################################### |
|
|
| 477 | # print |
|
|
| 478 | # |
|
|
| 479 | # CGI::p('The percentage of active students with no more than the indicated number of total attempts'), |
|
|
| 480 | # CGI::start_table({-border=>1}), |
|
|
| 481 | # CGI::Tr( |
|
|
| 482 | # CGI::td( ['% students', |
|
|
| 483 | # (map { " ".(100-$_) } @brackets, 0) , |
|
|
| 484 | # |
|
|
| 485 | # ] |
|
|
| 486 | # ) |
|
|
| 487 | # ); |
|
|
| 488 | # |
|
|
| 489 | # |
|
|
| 490 | # foreach my $probID (@problemIDs) { |
|
|
| 491 | # print CGI::Tr( |
|
|
| 492 | # CGI::td( [ |
|
|
| 493 | # "Prob $probID", |
|
|
| 494 | # (map { '≤ '.sprintf("%0.0f",$attempts_percentiles_for_problem{$probID}->{$_}) } @brackets, 0), |
|
|
| 495 | # |
|
|
| 496 | # ] |
|
|
| 497 | # ) |
|
|
| 498 | # ); |
|
|
| 499 | # |
|
|
| 500 | # } |
|
|
| 501 | # print CGI::end_table(); |
|
|
| 502 | # ##################################################################################### |
|
|
| 503 | # # construct header |
|
|
| 504 | # my $problem_header = ''; |
|
|
| 505 | # |
|
|
| 506 | # foreach my $i (1..$max_num_problems) { |
|
|
| 507 | # $problem_header .= CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=p$i"},threeSpaceFill($i) ); |
|
|
| 508 | # } |
|
|
| 509 | # print |
|
|
| 510 | # CGI::p("Details"), |
|
|
| 511 | # defined($sort_method_name) ?"sort method is $sort_method_name":"", |
|
|
| 512 | # CGI::start_table({-border=>5,style=>'font-size:smaller'}), |
|
|
| 513 | # CGI::Tr(CGI::th( {-align=>'center'}, |
|
|
| 514 | # [CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=name"},'Name'), |
|
|
| 515 | # CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=score"},'Score'), |
|
|
| 516 | # 'Out'.CGI::br().'Of', |
|
|
| 517 | # CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=index"},'Ind'), |
|
|
| 518 | # '<pre>Problems'.CGI::br().$problem_header.'</pre>', |
|
|
| 519 | # CGI::a({"href"=>$url."?".$self->url_authen_args."&sort=section"},'Section'), |
|
|
| 520 | # 'Recitation', |
|
|
| 521 | # 'login_name', |
|
|
| 522 | # ]) |
|
|
| 523 | # |
|
|
| 524 | # ); |
|
|
| 525 | # |
|
|
| 526 | # foreach my $rec (@augmentedUserRecords) { |
|
|
| 527 | # my $fullName = join("", $rec->{first_name}," ", $rec->{last_name}); |
|
|
| 528 | # my $email = $rec->{email_address}; |
|
|
| 529 | # my $twoString = $rec->{twoString}; |
|
|
| 530 | # print CGI::Tr( |
|
|
| 531 | # CGI::td(CGI::a({-href=>$rec->{act_as_student}},$fullName), CGI::br(), CGI::a({-href=>"mailto:$email"},$email)), |
|
|
| 532 | # CGI::td( sprintf("%0.2f",$rec->{score}) ), # score |
|
|
| 533 | # CGI::td($rec->{total}), # out of |
|
|
| 534 | # CGI::td(sprintf("%0.0f",100*($rec->{index}) )), # indicator |
|
|
| 535 | # CGI::td($rec->{problemString}), # problems |
|
|
| 536 | # CGI::td($self->nbsp($rec->{section})), |
|
|
| 537 | # CGI::td($self->nbsp($rec->{recitation})), |
|
|
| 538 | # CGI::td($rec->{user_id}), |
|
|
| 539 | # |
|
|
| 540 | # ); |
|
|
| 541 | # } |
|
|
| 542 | # |
|
|
| 543 | # print CGI::end_table(); |
|
|
| 544 | # |
|
|
| 545 | # |
|
|
| 546 | # |
|
|
| 547 | # |
|
|
| 548 | # return ""; |
|
|
| 549 | # } |
|
|
| 550 | sub displayStudentStats { |
183 | sub displayStudentStats { |
| 551 | my $self = shift; |
184 | my ($self, $studentName) = @_; |
| 552 | my $studentName = shift; |
|
|
| 553 | my $r = $self->{r}; |
185 | my $r = $self->r; |
| 554 | my $db = $self->{db}; |
186 | my $db = $r->db; |
| 555 | my $ce = $self->{ce}; |
187 | my $ce = $r->ce; |
|
|
188 | |
| 556 | my $courseName = $ce->{courseName}; |
189 | my $courseName = $ce->{courseName}; |
| 557 | my $studentRecord = $db->getUser($studentName); # checked |
190 | my $studentRecord = $db->getUser($studentName); # checked |
| 558 | die "record for user $studentName not found" unless $studentRecord; |
191 | die "record for user $studentName not found" unless $studentRecord; |
| 559 | my $root = $ce->{webworkURLs}->{root}; |
192 | my $root = $ce->{webworkURLs}->{root}; |
| 560 | |
193 | |