Diff for /loncom/interface/statistics/lonstudentassessment.pm between versions 1.1 and 1.30

version 1.1, 2002/07/24 14:52:32 version 1.30, 2003/02/28 21:19:00
Line 1 Line 1
 # The LearningOnline Network with CAPA  # The LearningOnline Network with CAPA
 # (Publication Handler  
 #  #
 # $Id$  # $Id$
 #  #
 # Copyright Michigan State University Board of Trustees  # Copyright Michigan State University Board of Trustees
 #  #
 # This file is part of the LearningOnline Network with CAPA (LON-CAPA).  # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
 #  
 # LON-CAPA is free software; you can redistribute it and/or modify  # LON-CAPA is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by  # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation; either version 2 of the License, or  # the Free Software Foundation; either version 2 of the License, or
Line 26 Line 24
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
 # (Navigate problems for statistical reports  # (Navigate problems for statistical reports
 # YEAR=2001  
 # 5/5,7/9,7/25/1,8/11,9/13,9/26,10/5,10/9,10/22,10/26 Behrouz Minaei  
 # 11/1,11/4,11/16,12/14,12/16,12/18,12/20,12/31 Behrouz Minaei  
 # YEAR=2002  
 # 1/22,2/1,2/6,2/25,3/2,3/6,3/17,3/21,3/22,3/26,4/7,5/6 Behrouz Minaei  
 # 5/12,5/14,5/15,5/19,5/26,7/16  Behrouz Minaei  
 #  #
 ###  #######################################################
   #######################################################
   
   =pod
   
   =head1 NAME
   
   lonstudentassessment
   
   =head1 SYNOPSIS
   
   Presents assessment data about a student or a group of students.
   
   =head1 Subroutines
   
   =over 4 
   
   =cut
   
 package Apache::lonstudentassessment;   #######################################################
   #######################################################
   
   package Apache::lonstudentassessment;
   
 use strict;  use strict;
   use Apache::lonstatistics;
 use Apache::lonhtmlcommon;  use Apache::lonhtmlcommon;
 use Apache::loncoursedata;  use Apache::loncoursedata;
   use Apache::lonnet; # for logging porpoises
 use GDBM_File;  use GDBM_File;
   
   my $Statistics;
   
   #######################################################
   #######################################################
   
   =pod
   
   =item &BuildStudentAssessmentPage()
   
   Inputs: 
   
   =over 4
   
   =item $cacheDB The name of the cache file used to store student data
   
   =item $students Array ref containing the name(s) of the students 
   selected for display
   
   =item $courseID The ID of the course
   
   =item $formName The name of the html form - 'Statistics'
   
   =item $headings Array ref of headings to show
   
   =item $spacing A string of spaces
   
   =item $studentInformation Array ref of possible headings for student info
   ('fullname','section',...)
   
   =item $r Apache Request
   
   =item $c Apache Connection 
   
   =back
   
   =cut
   
   #######################################################
   #######################################################
 sub BuildStudentAssessmentPage {  sub BuildStudentAssessmentPage {
     my ($cacheDB, $students, $courseID, $c)=@_;      my ($r,$c)=@_;
       undef($Statistics);
       
       #
       $r->print(&CreateInterface());
       $r->rflush();
       #
       $r->print(&CreateTableHeadings());
       if($c->aborted()) {  return ; }
       
       my $Count = 0;
       $r->print('<pre>'."\n");
       foreach my $student (@Apache::lonstatistics::Students) {
           if($c->aborted()) { return ; }
           $r->print(&ChartOutputStudent($student));
           # output it
   
           $Count++;
           if($Count % 5 == 0) {
               $r->print("</pre>\n<pre>");
           }
   
     my %cache;          $r->rflush();
       }
       $r->print('</pre>'."\n"); 
       my $Str;
       return;
   }
   
     my $Ptr = '';  #######################################################
     $Ptr .= '<table border="0"><tbody>';  #######################################################
   
     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER,0640)) {  =pod
         return '<html><body>Unable to tie database.</body></html>';  
     }  =item &CreateInterface()
   
     my $selectedName = $cache{'StudentAssessmentStudent'};  Called by &BuildStudentAssessmentPage to create the top part of the
     for(my $index=0;   page which displays the chart.
         ($selectedName ne 'All Students') && ($index<(scalar @$students));   
         $index++) {  Inputs: None
         my $fullname = $cache{$students->[$index].':fullname'};  
         if($fullname eq $selectedName) {  Returns:  A string containing the HTML for the headers and top table for 
             if($cache{'StudentAssessmentMove'} eq 'next') {  the chart page.
                 if($index == ((scalar @$students) - 1)) {  
                     $selectedName = $students->[0];  =cut
                 } else {  
                     $selectedName = $students->[$index+1];  #######################################################
                 }  #######################################################
             } elsif($cache{'StudentAssessmentMove'} eq 'previous') {  sub CreateInterface {
                 if($index == 0) {      my $Str = '';
                     $selectedName = $students->[-1];  #    $Str .= &CreateLegend();
                 } else {      $Str .= '<table cellspacing="5">'."\n";
                     $selectedName = $students->[$index-1];      $Str .= '<tr>';
                 }      $Str .= '<td align="center"><b>Sections</b></td>';
             } else {      $Str .= '<td align="center"><b>Student Data</b></td>';
                 $selectedName = $students->[$index];      $Str .= '<td align="center"><b>Sequences and Folders</b></td>';
             }      $Str .= '</tr>'."\n";
       #
       $Str .= '<tr><td align="center">'."\n";
       $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
       $Str .= '</td><td align="center">';
       my $only_seq_with_assessments = sub { 
           my $s=shift;
           if ($s->{'num_assess'} < 1) { 
               return 0;
           } else { 
               return 1;
           }
       };
       $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
                                                         5,undef);
       $Str .= '</td><td>'."\n";
       $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
                                                 $only_seq_with_assessments);
       $Str .= '</td></tr>'."\n";
       $Str .= '</table>'."\n";
       return $Str;
   }
   
   
   #######################################################
   #######################################################
   
   =pod
   
   =item Table Output Routines
   
   =over 4
   
   =cut
   
   #######################################################
   #######################################################
   {
       my $padding;
   
   #######################################################
   #######################################################
   
   =pod
   
   =item &CreateTableHeadings()
   
   Create HTML for the columns of student data to show.
   Called by &BuildStudentAssessmentPage().  Calls
   &Apache::lonhtmlcommon::CreateHeadings().
   
   Inputs:
   
   =over 4
   
   =item $cache The ubiquitous cache
   
   =item $spacing A string of spaces
   
   =item $infoKeys Array ref to names of keys to display from the cache 
   which describe students
   
   =item $infoHeadings Array ref to headings of columns for student info
   
   =item $sequenceKeys Array ref of names of keys to use to retrieve sequence
   data from the cache
   
   =item $sequenceHeadings Array ref of names of sequences used for output.
   
   =back
   
   Returns: A string containing the HTML of the table headings.
   
   =cut
   
   #######################################################
   #######################################################
   sub CreateTableHeadings {
       #
       $padding = ' 'x3;
       #
       my $Str = '<pre>';
       # First, the @StudentData fields need to be listed
       my @to_show = @Apache::lonstatistics::SelectedStudentData;
       foreach (@to_show) {
           if ($_ eq 'all') {
               @to_show = @Apache::lonstatistics::StudentDataOrder;
             last;              last;
         }          }
     }      }
       foreach my $field (@to_show) {
           my $title=$Apache::lonstatistics::StudentData{$field}->{'title'};
           my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'};
           my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
           $Str .= $title.' 'x($width-$base).$padding;
       }
       # Now the selected sequences need to be listed
       foreach my $map_symb (@Apache::lonstatistics::SelectedMaps) {
           foreach my $sequence (@Apache::lonstatistics::Sequences) {
               next if ($sequence->{'symb'} ne $map_symb && $map_symb ne 'all');
               next if ($sequence->{'num_assess'} < 1);
               my $title = $sequence->{'title'};
               my $base  = $sequence->{'base_width'};
               my $width = $sequence->{'width'};
               $Str .= $title.' 'x($width-$base).$padding;
           }
       }
       $Str .= 'total';
       $Str .= "</pre>\n";
       return $Str;
   }
   
   #######################################################
   #######################################################
   
     $Ptr .= '<tr><td align="right"><b>Select Map</b></td>'."\n";  =pod
     $Ptr .= '<td align="left">';  
     $Ptr .= &Apache::lonhtmlcommon::MapOptions(\%cache, 'StudentAssessment');  
     $Ptr .= '</td></tr>'."\n";  
     $Ptr .= '<tr><td align="right"><b>Select Student</b></td>'."\n";  
     $Ptr .= '<td align="left">'."\n";  
     $Ptr .= &Apache::lonhtmlcommon::StudentOptions(\%cache, $students,   
                                                    $selectedName,   
                                                    'StudentAssessment');  
     $Ptr .= '</td></tr>'."\n";  
     untie(%cache);  
   
     $Ptr .= '<tr><td></td><td align="left">';  
     $Ptr .= '<input type="submit" name="CreateStudentAssessment" ';  
     $Ptr .= 'value="Create Student Report" />';  
     $Ptr .= '&nbsp&nbsp&nbsp';  
     $Ptr .= '<input type="submit" name="PreviousStudent" ';  
     $Ptr .= 'value="Previous Student" />';  
     $Ptr .= '&nbsp&nbsp&nbsp';  
     $Ptr .= '<input type="submit" name="NextStudent" ';  
     $Ptr .= 'value="Next Student" />';  
     $Ptr .= '&nbsp&nbsp&nbsp';  
     $Ptr .= '</td></tr></tbody></table>'."\n";  
   
     if($selectedName eq 'No Student Selected') {  
  $Ptr .= '<h3><font color=blue>WARNING: ';  
         $Ptr .= 'Please select a student</font></h3>';  
         return $Ptr;  
     }  
   
     my $selected=0;  
     foreach (@$students) {  
         next if ($_ ne $selectedName &&   
                  $selectedName ne 'All Students');  
         $selected = 1;  
         my $courseData =   
             &Apache::loncoursedata::DownloadCourseInformation($_, $courseID);  
         last if ($c->aborted());  
         if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT,0640)) {  
             &Apache::loncoursedata::ProcessStudentData(\%cache,   
                                                        $courseData, $_);  
             if(!$c->aborted()) { $Ptr .= &StudentReport(\%cache, $_); }  
             untie(%cache);  
         }  
     }  
     if($selected == 0) {  
  $Ptr .= '<h3><font color=blue>WARNING: ';  
         $Ptr .= 'Please select a student</font></h3>';  
     }  
   
     return $Ptr;  
 }  
   
 #---- Student Assessment Web Page --------------------------------------------  
   
 # ------ Create different Student Report   
 sub StudentReport {  
     my ($cache, $name)=@_;  
   
   =item &ChartOutputStudent($student)
   
   Return a line of the chart for a student.
   
   =cut
   
   #######################################################
   #######################################################
   sub ChartOutputStudent {
       my $student = shift;
     my $Str = '';      my $Str = '';
     if($cache->{$name.':error'} =~ /course/) {      # First, the @StudentData fields need to be listed
         my ($username)=split(':',$name);      my @to_show = @Apache::lonstatistics::SelectedStudentData;
         $Str .= '<b><font color="blue">No course data for student </font>';      foreach (@to_show) {
         $Str .= '<font color="red">'.$username.'.</font></b><br>';          if ($_ eq 'all') {
               @to_show = @Apache::lonstatistics::StudentDataOrder;
               last;
           }
       }
       foreach my $field (@to_show) {
           my $title=$student->{$field};
           my $base =scalar(my @Tmp = split(//,$title));
           my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
           $Str .= $title.' 'x($width-$base).$padding;
       }
       # Get ALL the students data
       my %StudentsData;
       my @tmp = &Apache::loncoursedata::get_current_state
           ($student->{'username'},$student->{'domain'},undef,
            $ENV{'request.course.id'});
       if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
           %StudentsData = @tmp;
       }
       if (scalar(@tmp) < 1) {
           $Str .= '<font color="blue">No Course Data</font>'."\n";
         return $Str;          return $Str;
     }      }
       #
       # By sequence build up the data
       my $studentstats;
       foreach my $map_symb (@Apache::lonstatistics::SelectedMaps) {
           foreach my $seq (@Apache::lonstatistics::Sequences) {
               next if ($map_symb ne $seq->{'symb'} && $map_symb ne 'all');
               next if ($seq->{'num_assess'} < 1);
               my ($performance,$score,$seq_max) =
                   &StudentPerformanceOnSequence($student,\%StudentsData,
                                                 $seq,'linkify');
               $Str .= $performance.$padding;
               $studentstats->{$seq->{'symb'}}->{'score'}= $score;
               $studentstats->{$seq->{'symb'}}->{'max'}  = $seq_max;
           }
       }
       #
       # Total it up and store the statistics info.
       my ($score,$max) = (0,0);
       while (my ($symb,$seq_stats) = each (%{$studentstats})) {
           $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
           $Statistics->{$symb}->{'max'}   += $seq_stats->{'max'};
           $score += $seq_stats->{'score'};
           $max   += $seq_stats->{'max'};
       }
       my $scorelength = scalar(my @tmp1 = split(//,$score));
       my $maxlength   = scalar(my @tmp2 = split(//,$max));
       $Str .= ' '.' 'x($maxlength-$scorelength).$score.'/'.$max;
       $Str .= " \n";
       return $Str;
   }    
   
     $Str .= "<table border=2><tr><th> \# </th><th> Set Title </th>";  #######################################################
     $Str .= '<th> Results </th><th> Tries </th></tr>'."\n";  #######################################################
   
     my $codes;  =pod
     my $attempts;  
     foreach my $sequence (split(':', $cache->{'orderedSequences'})) {  
         if($cache->{'StudentAssessmentMap'} ne 'All Maps'  &&  
            $cache->{'StudentAssessmentMap'} ne $cache->{$sequence.':title'}) {  
             next;  
         }  
   
         $Str .= '<tr><td>'.$sequence.'</td>';  
         $Str .= '<td>'.$cache->{$sequence.':title'}.'</td>';  
   
         $codes = '';  
         $attempts = '';  
         foreach my $problemID (split(':', $cache->{$sequence.':problems'})) {  
             my $problem = $cache->{$problemID.':problem'};  
             my $LatestVersion = $cache->{$name.':version:'.$problem};  
   
             # Output dashes for all the parts of this problem if there  
             # is no version information about the current problem.  
             if(!$LatestVersion) {  
                 foreach my $part (split(/\:/,$cache->{$sequence.':'.  
                                                       $problemID.  
                                                       ':parts'})) {  
     $codes    .= "-,";  
                     $attempts .= "0,";   
                 }  
                 next;  
             }  
   
             my %partData=undef;  =back
             # Initialize part data, display skips correctly  
             # Skip refers to when a student made no submissions on that  
             # part/problem.  
             foreach my $part (split(/\:/,$cache->{$sequence.':'.  
                                                   $problemID.  
                                                   ':parts'})) {  
                 $partData{$part.':tries'}=0;  
                 $partData{$part.':code'}='-';  
             }  
   
             # Looping through all the versions of each part, starting with the  =cut
             # oldest version.  Basically, it gets the most recent   
             # set of grade data for each part.  #######################################################
     for(my $Version=1; $Version<=$LatestVersion; $Version++) {  #######################################################
                 foreach my $part (split(/\:/,$cache->{$sequence.':'.  
                                                       $problemID.  }
                                                       ':parts'})) {  
   #######################################################
                     if(!defined($cache->{$name.":$Version:$problem".  #######################################################
                                                ":resource.$part.solved"})) {  
                         # No grade for this submission, so skip  =pod
                         next;  
                     }  =item &StudentPerformanceOnSequence()
   
   Inputs:
   
   =over 4
   
   =item $student
   
                     my $tries=0;  =item $studentdata Hash ref to all student data
                     my $code='U';  
   
                     $tries = $cache->{$name.":$Version:$problem".  =item $seq Hash ref, the sequence we are working on
                                       ":resource.$part.tries"};  
                     $partData{$part.':tries'}=($tries) ? $tries : 0;  =item $links if defined we will output links to each resource.
   
                     my $val = $cache->{$name.":$Version:$problem".  =back
                                        ":resource.$part.solved"};  
                     if    ($val eq 'correct_by_student')   {$code = 'Y';}   =cut
                     elsif ($val eq 'correct_by_override')  {$code = 'y';}  
                     elsif ($val eq 'incorrect_attempted')  {$code = 'N';}   #######################################################
                     elsif ($val eq 'incorrect_by_override'){$code = 'N';}  #######################################################
                     elsif ($val eq 'excused')              {$code = 'x';}  sub StudentPerformanceOnSequence {
                     $partData{$part.':code'}=$code;      my ($student,$studentdata,$seq,$links) = @_;
       my $Str = '';
       my $output_width = 0;
       my ($sum,$max) = (0,0);
       foreach my $resource (@{$seq->{'contents'}}) {
           next if ($resource->{'type'} ne 'assessment');
           my $resource_data = $studentdata->{$resource->{'symb'}};
           my $value = '';
           foreach my $partnum (@{$resource->{'parts'}}) {
               $max++;
               my $symbol = ' '; # default to space
               #
               if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
                   my $status = $resource_data->{'resource.'.$partnum.'.solved'};
                   if ($status eq 'correct_by_override') {
                       $symbol = '+';
                       $sum++;
                   } elsif ($status eq 'incorrect_by_override') {
                       $symbol = '-';
                   } elsif ($status eq 'ungraded_attempted') {
                       $symbol = '#';
                   } elsif ($status eq 'incorrect_attempted')  {
                       $symbol = '.';
                   } elsif ($status eq 'excused') {
                       $symbol = 'x';
                       $max--;
                   } elsif ($status eq 'correct_by_student' &&
                       exists($resource_data->{'resource.'.$partnum.'.tries'})){
                       my $num = $resource_data->{'resource.'.$partnum.'.tries'};
                       if ($num > 9) {
                           $symbol = '*';
                       } elsif ($num > 0) {
                           $symbol = $num;
                       } else {
                           $symbol = ' ';
                       }
                       $sum++;
                   } else {
                       $symbol = ' ';
                   }
               } else {
                   # Unsolved.  Did they try?
                   if (exists($resource_data->{'resource.'.$partnum.'.tries'})){
                       $symbol = '.';
                   } else {
                       $symbol = ' ';
                 }                  }
             }              }
               #
             # Loop through all the parts for the current problem in the               $output_width++;
             # correct order and prepare the output              if (defined($links) && $symbol ne ' ') {
             foreach (split(/\:/,$cache->{$sequence.':'.$problemID.                  $symbol = '<a href="/adm/grades'.
                                          ':parts'})) {                      '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
                 $codes    .= $partData{$_.':code'}.',';                          '&student='.$student->{'username'}.
                 $attempts .= $partData{$_.':tries'}.',';                               '&domain='.$student->{'domain'}.
                                   '&command=submission">'.$symbol.'</a>';
             }              }
               $value .= $symbol;
           }
           $Str .= $value;
       }
       # Put on the totals
       my $ratio = $sum.'/'.$max;
       my $ratio_length = scalar(my @tmp1 = split(//,$ratio));
       # Pad with extra spaces
       my $width = $seq->{'width'};
       $Str .= ' 'x($width-$output_width-$ratio_length).$ratio;
       #
       return ($Str,$sum,$max);
   }
   
   #######################################################
   #######################################################
   sub StudentAverageTotal {
       my ($cache, $students, $sequenceKeys)=@_;
       my $Str = "\n<b>Summary Tables:</b>\n";
       my %Correct = ();
       my $ProblemsSolved = 0;
       my $TotalProblems = 0;
       my $StudentCount = 0;
   
       foreach my $name (@$students) {
           $StudentCount++;
           foreach my $sequence (@$sequenceKeys) {
               $Correct{$sequence} +=
          $cache->{$name.':'.$sequence.':problemsCorrect'};
         }          }
         $codes    =~ s/,$//;   $ProblemsSolved += $cache->{$name.':problemsSolved'};
         $attempts =~ s/,$//;          $TotalProblems += $cache->{$name.':totalProblems'};
         $Str .= '<td>'.$codes.'</td>';      }
         $Str .= '<td>'.$attempts.'</td>';      if ($StudentCount) { 
         $Str .= '</tr>'."\n";          $ProblemsSolved = sprintf( "%.2f", 
                                $ProblemsSolved/$StudentCount);
           $TotalProblems /= $StudentCount;
       } else {
           $ProblemsSolved = 0;
           $TotalProblems  = 0;
     }      }
   
       $Str .= '<table border=2 cellspacing="1">'."\n";
       $Str .= '<tr><td><b>Students Count</b></td><td><b>'.
               $StudentCount.'</b></td></tr>'."\n";
       $Str .= '<tr><td><b>Total Problems</b></td><td><b>'.
               $TotalProblems.'</b></td></tr>'."\n";
       $Str .= '<tr><td><b>Average Correct</b></td><td><b>'.
               $ProblemsSolved.'</b></td></tr>'."\n";
     $Str .= '</table>'."\n";      $Str .= '</table>'."\n";
   
       $Str .= '<table border=2 cellspacing="1">'."\n";
       $Str .= '<tr><th>Title</th><th>Total Problems</th>'.
               '<th>Average Correct</th></tr>'."\n";
       foreach my $S(@$sequenceKeys) {
           my $title=$cache->{$S.':title'};
    #$Str .= $cache->{$S.':problems'};
    #my @problems=split(':', $cache->{$S.':problems'});
    #my $pCount=scalar @problems;
    my $pCount=MaxSeqPr($cache,@$students[0],$S);
           my $crr;
    if ($StudentCount) {
               $crr=sprintf( "%.2f", $Correct{$S}/$StudentCount );
           } else {
               $crr="0.00";
           }
           $Str .= '<tr><td>'.$title.
                   '</td><td align=center>'.$pCount.
                   '</td><td align=center>'.$crr.
                   '</td></tr>'."\n";
       }
   
       $Str .= '</table>'."\n";
   
       return $Str;
   }
   
   #######################################################
   #######################################################
   
   =pod
   
   =item &CreateLegend()
   
   This function returns a formatted string containing the legend for the
   chart.  The legend describes the symbols used to represent grades for
   problems.
   
   =cut
   
   #######################################################
   #######################################################
   sub CreateLegend {
       my $Str = "<p><pre>".
                 "   1  correct by student in 1 try\n".
                 "   7  correct by student in 7 tries\n".
                 "   *  correct by student in more than 9 tries\n".
         "   +  correct by hand grading or override\n".
                 "   -  incorrect by override\n".
         "   .  incorrect attempted\n".
         "   #  ungraded attempted\n".
                 "      not attempted (blank field)\n".
         "   x  excused".
                 "</pre><p>";
     return $Str;      return $Str;
 }  }
   
 #---- END Student Assessment Web Page ----------------------------------------  #######################################################
   #######################################################
   
   =pod 
   
   =back
   
   =cut
   
   #######################################################
   #######################################################
   
 1;  1;
   
 __END__  __END__

Removed from v.1.1  
changed lines
  Added in v.1.30


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>