[system] / branches / rel-2-4-dev / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / Stats.pm Repository:
ViewVC logotype

View of /branches/rel-2-4-dev/webwork2/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1769 - (download) (as text) (annotate)
Sat Jan 31 14:47:44 2004 UTC (9 years, 3 months ago) by gage
Original Path: trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm
File size: 17601 byte(s)
Replaced "getuserProblems by getAllUserProblems when printing statistics for a single user (many sets)
The time when from 178 to 35 using GDBM.  It went from 15sec to 16sec (on a different course)
when using sql.

--Mike

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm,v 1.16 2004/01/31 04:08:57 gage Exp $
    5 #
    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 ################################################################################
   16 
   17 package WeBWorK::ContentGenerator::Instructor::Stats;
   18 use base qw(WeBWorK::ContentGenerator::Instructor);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor::Stats - Display statistics by user or
   23 problem set.
   24 
   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   my $self     = shift;
   36   # FIXME  are there args here?
   37   my $type       = shift || '';
   38   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 #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   $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 }
   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 
   70   return $self->pathMacro($args,
   71     "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   );
   88 }
   89 
   90 sub title {
   91   my ($self, @components) = @_;
   92   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 }
  101 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 
  115   }
  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   my @setLinks      = ();
  131   my @studentLinks  = ();
  132   foreach my $set (@setList) {
  133     push @setLinks, CGI::a({-href=>"${uri}set/$set/?".$self->url_authen_args },"set $set" );
  134   }
  135 
  136   foreach my $student (@studentList) {
  137     push @studentLinks, CGI::a({-href=>"${uri}student/$student/?".$self->url_authen_args},"  $student" ),;
  138   }
  139   print join("",
  140     CGI::start_table({-border=>2, -cellpadding=>20}),
  141     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 
  154 }
  155 sub displaySets {
  156   my $self    = shift;
  157   #FIXME
  158   my $setName = shift;
  159 
  160   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   my $courseName = $ce->{courseName};
  166   my $setRecord  = $db->getGlobalSet($setName); # checked
  167   die "global set $setName  not found." unless $setRecord;
  168   my $root       = $ce->{webworkURLs}->{root};
  169   my $url        = $r->uri;
  170   my $sort_method_name = $r->param('sort');
  171   my @studentList   = $db->listUsers;
  172 
  173   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         my $left  =  $b->{problemData}->{$1} ||0;
  181         my $right =  $a->{problemData}->{$1} ||0;
  182       return $left <=> $right;  # sort by number of attempts.
  183     }
  184 
  185   };
  186   #FIXME  need to be able to sort by index and score as well.
  187 ###############################################################
  188 #  Print table
  189 ###############################################################
  190   my @problems = sort {$a <=> $b } $db->listUserProblems($user, $setName);
  191 
  192   # FIXME I'm assuming the problems are all the same
  193 
  194   my $num_of_problems  = @problems;
  195   # get user records
  196   $WeBWorK::timer->continue("Begin obtaining user records for set $setName") if defined($WeBWorK::timer);
  197   my @userRecords  = $db->getUsers(@studentList);
  198   $WeBWorK::timer->continue("End obtaining user records for set $setName") if defined($WeBWorK::timer);
  199     $WeBWorK::timer->continue("begin main loop") if defined($WeBWorK::timer);
  200   my @augmentedUserRecords    = ();
  201   foreach my $studentRecord (@userRecords)   {
  202     next unless ref($studentRecord);
  203     my $student = $studentRecord->user_id;
  204     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     my %h_problemData  = ();
  215     my $probNum         = 0;
  216     my @triplets = map {[$student, $setName, $_ ]} @problems;
  217     $WeBWorK::timer->continue("Begin obtaining problem records for user $student set $setName") if defined($WeBWorK::timer);
  218     #my @problemRecords = $db->getUserProblems( @triplets );
  219     my @problemRecords = $db->getAllUserProblems( $student, $setName );
  220     $WeBWorK::timer->continue("End obtaining problem records for user $student set $setName") if defined($WeBWorK::timer);
  221 
  222     foreach my $problemRecord (@problemRecords) {
  223       next unless ref($problemRecord);
  224       my $prob = $problemRecord->problem_id;
  225     #foreach my $prob (@problems) {
  226       #my $problemRecord   = $db->getUserProblem($student, $setName, $prob);
  227       $probNum++;
  228       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       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       $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       $h_problemData{$probNum} = $incorrect;
  267     }
  268     # FIXME   we can do this more effficiently  get the list first
  269 
  270 
  271     my $act_as_student_url = "$root/$courseName/$setName?user=".$r->param("user").
  272       "&effectiveUser=".$studentRecord->user_id()."&key=".$r->param("key");
  273     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     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   $WeBWorK::timer->continue("end mainloop") if defined($WeBWorK::timer);
  295 
  296   @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     CGI::start_table({-border=>5,style=>'font-size:smaller'}),
  310     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     print CGI::Tr(
  328       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 
  337     );
  338   }
  339 
  340   print CGI::end_table();
  341 
  342 
  343 
  344 
  345   return "";
  346 }
  347 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   my $studentRecord = $db->getUser($studentName); # checked
  355   die "record for user $studentName not found" unless $studentRecord;
  356   my $root = $ce->{webworkURLs}->{root};
  357 
  358   my @setIDs    = sort $db->listUserSets($studentName);
  359   my $fullName = join("", $studentRecord->first_name," ", $studentRecord->last_name);
  360   my $act_as_student_url = "$root/$courseName/?user=".$r->param("user").
  361       "&effectiveUser=".$studentRecord->user_id()."&key=".$r->param("key");
  362 
  363   my $email    = $studentRecord->email_address;
  364   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 
  370   ###############################################################
  371   #  Print table
  372   ###############################################################
  373 
  374   # FIXME I'm assuming the problems are all the same
  375   # FIXME what does this mean?
  376 
  377   my @rows;
  378   my $max_problems=0;
  379 
  380   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     $max_problems = $num_of_problems if $num_of_problems > $max_problems;
  392     # construct header
  393     $WeBWorK::timer->continue("Begin collecting problems for set $setName") if defined($WeBWorK::timer);
  394     #my @problemRecords = $db->getUserProblems( map {[$studentName, $setName,$_]}  @problems);
  395     my @problemRecords = $db->getAllUserProblems( $studentName, $setName );
  396     $WeBWorK::timer->continue("End collecting problems for set $setName") if defined($WeBWorK::timer);
  397     foreach my $problemRecord (@problemRecords) {
  398       my $prob = $problemRecord->problem_id;
  399     #foreach my $prob (@problems) {
  400       #my $problemRecord   = $db->getUserProblem($studentName, $setName, $prob);
  401 
  402       my $valid_status    = 0;
  403       unless (defined($problemRecord) ){
  404         # warn "Can't find record for problem $prob in set $setName for $student";
  405         # FIXME check the legitimate reasons why a student record might not be defined
  406         next;
  407       }
  408         $status             = $problemRecord->status || 0;
  409           $attempted          = $problemRecord->attempted;
  410       if (!$attempted){
  411         $longStatus     = '.  ';
  412       }
  413       elsif   ($status >= 0 and $status <=1 ) {
  414         $valid_status   = 1;
  415         $longStatus     = int(100*$status+.5);
  416         if ($longStatus == 100) {
  417           $longStatus = 'C  ';
  418         }
  419         else {
  420           $longStatus = &threeSpaceFill($longStatus);
  421         }
  422       }
  423       else  {
  424         $longStatus   = 'X  ';
  425       }
  426 
  427       my $incorrect     = $problemRecord->num_incorrect;
  428       $incorrect        = ($incorrect < 99) ? $incorrect: 99;  # take min
  429       $string          .=  $longStatus;
  430       $twoString       .= threeSpaceFill($incorrect);
  431       my $probValue     = $problemRecord->value;
  432       $probValue        = 1 unless defined($probValue);  # FIXME?? set defaults here?
  433       $total           += $probValue;
  434       $totalRight      += round_score($status*$probValue) if $valid_status;
  435       my $num_correct   = $problemRecord->num_incorrect || 0;
  436       my $num_incorrect = $problemRecord->num_correct   || 0;
  437       $num_of_attempts += $num_correct + $num_incorrect;
  438     }
  439 
  440     # FIXME   we can do this more effficiently  get the list first
  441 
  442 
  443     # FIXME  this needs formatting
  444 
  445     my $avg_num_attempts = ($num_of_problems) ? $num_of_attempts/$num_of_problems : 0;
  446     my $successIndicator = ($avg_num_attempts) ? ($totalRight/$total)**2/$avg_num_attempts : 0 ;
  447 
  448     push @rows, CGI::Tr(
  449       CGI::td($setName),
  450       CGI::td(sprintf("%0.2f",$totalRight)), # score
  451       CGI::td($total), # out of
  452       CGI::td(sprintf("%0.0f",100*$successIndicator)),   # indicator
  453       CGI::td("<pre>$string\n$twoString</pre>"), # problems
  454       #CGI::td($studentRecord->section),
  455       #CGI::td($studentRecord->recitation),
  456       #CGI::td($studentRecord->user_id),
  457 
  458     );
  459 
  460   }
  461 
  462   my $problem_header = "";
  463   foreach (1 .. $max_problems) {
  464     $problem_header .= &threeSpaceFill($_);
  465   }
  466 
  467   my $table_header = join("\n",
  468     CGI::start_table({-border=>5}),
  469     CGI::Tr(
  470       CGI::th({ -align=>'center',},'Set'),
  471       CGI::th({ -align=>'center', },'Score'),
  472       CGI::th({ -align=>'center', },'Out'.CGI::br().'Of'),
  473       CGI::th({ -align=>'center', },'Ind'),
  474       CGI::th({ -align=>'center', },'Problems'.CGI::br().CGI::pre($problem_header)),
  475       #CGI::th({ -align=>'center', },'Section'),
  476       #CGI::th({ -align=>'center', },'Recitation'),
  477       #CGI::th({ -align=>'center', },'login_name'),
  478       #CGI::th({ -align=>'center', },'ID'),
  479     )
  480   );
  481 
  482   print $table_header;
  483   print @rows;
  484   print CGI::end_table();
  485 
  486   return "";
  487 }
  488 
  489 #################################
  490 # Utility function NOT a method
  491 #################################
  492 sub threeSpaceFill {
  493     my $num = shift @_ || 0;
  494 
  495     if ($num < 10) {return "$num".'  ';}
  496     elsif ($num < 100) {return "$num".' ';}
  497     else {return "$num";}
  498 }
  499 sub round_score{
  500   return shift;
  501 }
  502 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9