--- loncom/interface/statistics/lonstudentassessment.pm 2002/07/24 14:52:32 1.1 +++ loncom/interface/statistics/lonstudentassessment.pm 2002/07/25 21:23:51 1.2 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # (Publication Handler # -# $Id: lonstudentassessment.pm,v 1.1 2002/07/24 14:52:32 stredwic Exp $ +# $Id: lonstudentassessment.pm,v 1.2 2002/07/25 21:23:51 stredwic Exp $ # # Copyright Michigan State University Board of Trustees # @@ -43,125 +43,190 @@ use Apache::loncoursedata; use GDBM_File; sub BuildStudentAssessmentPage { - my ($cacheDB, $students, $courseID, $c)=@_; + my ($cacheDB,$students,$courseID,$formName,$headings,$spacing, + $studentInformation,$r,$c)=@_; my %cache; - - my $Ptr = ''; - $Ptr .= ''; - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER,0640)) { - return 'Unable to tie database.'; - } - - my $selectedName = $cache{'StudentAssessmentStudent'}; - for(my $index=0; - ($selectedName ne 'All Students') && ($index<(scalar @$students)); - $index++) { - my $fullname = $cache{$students->[$index].':fullname'}; - if($fullname eq $selectedName) { - if($cache{'StudentAssessmentMove'} eq 'next') { - if($index == ((scalar @$students) - 1)) { - $selectedName = $students->[0]; - } else { - $selectedName = $students->[$index+1]; - } - } elsif($cache{'StudentAssessmentMove'} eq 'previous') { - if($index == 0) { - $selectedName = $students->[-1]; - } else { - $selectedName = $students->[$index-1]; - } - } else { - $selectedName = $students->[$index]; - } - last; - } + $r->print('Unable to tie database.'); + return; } + my $selectedName = &FindSelectedStudent(\%cache, + $cache{'StudentAssessmentStudent'}, + $students); + $r->print(&CreateInterface(\%cache, $selectedName, $students, $formName)); - $Ptr .= ''."\n"; - $Ptr .= ''."\n"; - $Ptr .= ''."\n"; - $Ptr .= ''."\n"; - untie(%cache); - - $Ptr .= ''; +} + +=pod + +=item &CreateColumnSelectors() + +This function generates the checkboxes above the column headings. The +column will be removed if the checkbox is unchecked. + +=over 4 + +Input: $CacheData, $headings + +$CacheData: A pointer to a hash tied to the cached data + +$headings: An array of the names of the columns for the student +information. They are used to know what are the student information columns + +Output: $present + +$present: The string contains the first row of a table. Each column contains +a checkbox which is left justified. Currently left justification is used +for consistency of location over the column in which it presides. + +=back + +=cut + +sub CreateColumnSelectors { + my ($headings)=@_; +=pod + my $found=0; + my ($name, $length, $position); + + my $present = ''; + for(my $index=0; $index<(scalar @$headings); $index++) { + $present .= ''; + $found++; + } + + foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) { + if(!&ShouldShowColumn($CacheData, 'ChartSequence'.$sequence)) { + next; + } + $present .= ''; + $found++; + } + + if(!$found) { + $present = ''; + } + + return $present.''."\n";; +=cut +} + #---- END Student Assessment Web Page ---------------------------------------- + +#---- Student Assessment Worker Functions ------------------------------------ + +sub FindSelectedStudent { + my($cache, $selectedName, $students)=@_; + for(my $index=0; + ($selectedName ne 'All Students') && ($index<(scalar @$students)); + $index++) { + my $fullname = $cache->{$students->[$index].':fullname'}; + if($fullname eq $selectedName) { + if($cache->{'StudentAssessmentMove'} eq 'next') { + if($index == ((scalar @$students) - 1)) { + $selectedName = $students->[0]; + } else { + $selectedName = $students->[$index+1]; + } + } elsif($cache->{'StudentAssessmentMove'} eq 'previous') { + if($index == 0) { + $selectedName = $students->[-1]; + } else { + $selectedName = $students->[$index-1]; + } + } else { + $selectedName = $students->[$index]; + } + last; + } + } + + return $selectedName; +} + +=pod + +=item &ShouldShowColumn() + +Determine if a specified column should be shown on the chart. + +=over 4 + +Input: $cache, $test + +$cache: A pointer to the hash tied to the cached data + +$test: The form name of the column (heading.$headingIndex) or +(sequence.$sequenceIndex) + +Output: 0 (false), 1 (true) + +=back + +=cut + +sub ShouldShowColumns { + my ($cache,$headings,$cacheKey)=@_; + + my @infoKeys=(); + my @infoHeadings=(); + + my @sequenceKeys=(); + my @sequenceHeadings=(); + + my $index; + for($index=0; $index < scalar @$headings; $index++) { + push(@infoHeadings, $headings->[$index]); + push(@infoKeys, $cacheKey->[$index]); + } + + foreach my $sequence (split(/\:/,$cache->{'orderedSequences'})) { + push(@sequenceHeadings, $cache->{$sequence.':title'}); + push(@sequenceKeys, $sequence); + } + +# my $headings=$cache->{'form.ChartHeadings'}; +# my $sequences=$cache->{'form.ChartSequences'}; +# if($headings eq 'ALLHEADINGS' || $sequences eq 'ALLSEQUENCES' || +# $headings=~/$test/ || $sequences=~/$test/) { +# return 1; +# } + + return (\@infoHeadings, \@infoKeys, \@sequenceHeadings, + \@sequenceKeys); +} + +#---- END Student Assessment Worker Functions -------------------------------- + 1; __END__
Select Map'; - $Ptr .= &Apache::lonhtmlcommon::MapOptions(\%cache, 'StudentAssessment'); - $Ptr .= '
Select Student'."\n"; - $Ptr .= &Apache::lonhtmlcommon::StudentOptions(\%cache, $students, - $selectedName, - 'StudentAssessment'); - $Ptr .= '
'; - $Ptr .= 'print($Ptr); + return; } + my ($infoHeadings, $infoKeys, $sequenceHeadings, $sequenceKeys) = + &ShouldShowColumns(\%cache, $headings, $studentInformation); + + $r->print(&CreateTableHeadings(\%cache, $spacing, $infoKeys, $infoHeadings, + $sequenceKeys, $sequenceHeadings)); + untie(%cache); + my $selected=0; + $r->print('
'."\n");
     foreach (@$students) {
         next if ($_ ne $selectedName && 
                  $selectedName ne 'All Students');
         $selected = 1;
-        my $courseData = 
-            &Apache::loncoursedata::DownloadCourseInformation($_, $courseID);
+        my $courseData; 
+        if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER,0640)) {
+            if($cache{$_.':lastDownloadTime'} eq 'Not downloaded') {
+                untie(%cache);
+                $courseData = 
+                    &Apache::loncoursedata::DownloadCourseInformation($_, 
+                                                                      $courseID);
+                if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT,0640)) {
+                    &Apache::loncoursedata::ProcessStudentData(\%cache, 
+                                                               $courseData, $_);
+                    untie(%cache);
+                } else {
+                    last if($c->aborted());
+                    next;
+                }
+            } else {
+                untie(%cache);
+            }
+        } else {
+            last if($c->aborted());
+            next;
+        }
+
         last if ($c->aborted());
-        if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT,0640)) {
-            &Apache::loncoursedata::ProcessStudentData(\%cache, 
-                                                       $courseData, $_);
-            if(!$c->aborted()) { $Ptr .= &StudentReport(\%cache, $_); }
+
+        if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER,0640)) {
+            my $displayString = 'DISPLAYDATA'.$spacing;
+            $r->print(&Apache::lonhtmlcommon::FormatStudentInformation(
+                                                         \%cache, $_,
+                                                         $infoKeys,
+                                                         $displayString,
+                                                         'preformatted'));
+            $r->print(&StudentReport(\%cache, $_, $spacing, $sequenceKeys));
+            $r->print("\n");
             untie(%cache);
         }
     }
+    $r->print('
'."\n"); if($selected == 0) { $Ptr .= '

WARNING: '; $Ptr .= 'Please select a student

'; + $r->print($Ptr); } - return $Ptr; + return; } #---- Student Assessment Web Page -------------------------------------------- -# ------ Create different Student Report +sub CreateInterface { + my($cache,$selectedName,$students,$formName)=@_; + my $Ptr = ''; + $Ptr .= &CreateLegend(); + $Ptr .= ''."\n"; + $Str .= &Apache::lonhtmlcommon::CreateHeadings($cache, + $infoKeys, + $infoHeadings, + $displayString, + 'preformatted'); + + $displayString = ''."\n"; + $Str .= &Apache::lonhtmlcommon::CreateHeadings($cache, + $sequenceKeys, + $sequenceHeadings, + $displayString, + 'preformatted'); + + $Str .= ''; + $Str .= '
'."\n"; + $Ptr .= '
DISPLAYDATA'.$spacing;
+    $displayString .= '
Total Solved/Total Problems
'."\n"; + + return $Str; +} + +=pod + +=item &FormatStudentData() + +First, FormatStudentInformation is called and prefixes the course information. +This function produces a formatted string of the student's course information. +Each column of data represents all the problems for a given sequence. For +valid grade data, a link is created for that problem to a submission record +for that problem. + +=over 4 + +Input: $name, $studentInformation, $ChartDB + +$name: The name and domain of the current student in name:domain format + +$studentInformation: A pointer to an array holding the names used to +remove data from the hash. They represent +the name of the data to be removed. + +$ChartDB: The name of the cached data database which will be tied to that +database. + +Output: $Str + +$Str: Formatted string that is an entire row of the chart. It is a +concatenation of student information and student course information. + +=back + +=cut + sub StudentReport { - my ($cache, $name)=@_; + my ($cache,$name,$spacing,$showSequences)=@_; + my ($username,$domain)=split(':',$name); my $Str = ''; if($cache->{$name.':error'} =~ /course/) { - my ($username)=split(':',$name); $Str .= 'No course data for student '; $Str .= ''.$username.'.
'; return $Str; } - $Str .= ""; - $Str .= ''."\n"; - - my $codes; - my $attempts; - foreach my $sequence (split(':', $cache->{'orderedSequences'})) { - if($cache->{'StudentAssessmentMap'} ne 'All Maps' && - $cache->{'StudentAssessmentMap'} ne $cache->{$sequence.':title'}) { - next; - } - - $Str .= ''; - $Str .= ''; - - $codes = ''; - $attempts = ''; + my $Version; + my $problemsCorrect = 0; + my $totalProblems = 0; + my $problemsSolved = 0; + my $numberOfParts = 0; +# foreach my $sequence (split(':', $cache->{'orderedSequences'})) { + foreach my $sequence (@$showSequences) { + my $characterCount=0; foreach my $problemID (split(':', $cache->{$sequence.':problems'})) { my $problem = $cache->{$problemID.':problem'}; my $LatestVersion = $cache->{$name.':version:'.$problem}; @@ -172,8 +237,9 @@ sub StudentReport { foreach my $part (split(/\:/,$cache->{$sequence.':'. $problemID. ':parts'})) { - $codes .= "-,"; - $attempts .= "0,"; + $Str .= ' '; + $totalProblems++; + $characterCount++; } next; } @@ -186,7 +252,7 @@ sub StudentReport { $problemID. ':parts'})) { $partData{$part.':tries'}=0; - $partData{$part.':code'}='-'; + $partData{$part.':code'}=' '; } # Looping through all the versions of each part, starting with the @@ -204,43 +270,317 @@ sub StudentReport { } my $tries=0; - my $code='U'; + my $code=' '; - $tries = $cache->{$name.":$Version:$problem". - ":resource.$part.tries"}; + $tries = $cache->{$name.':'.$Version.':'.$problem. + ':resource.'.$part.'.tries'}; $partData{$part.':tries'}=($tries) ? $tries : 0; - my $val = $cache->{$name.":$Version:$problem". - ":resource.$part.solved"}; - if ($val eq 'correct_by_student') {$code = 'Y';} - elsif ($val eq 'correct_by_override') {$code = 'y';} - elsif ($val eq 'incorrect_attempted') {$code = 'N';} - elsif ($val eq 'incorrect_by_override'){$code = 'N';} + my $val = $cache->{$name.':'.$Version.':'.$problem. + ':resource.'.$part.'.solved'}; + if ($val eq 'correct_by_student') {$code = '*';} + elsif ($val eq 'correct_by_override') {$code = '+';} + elsif ($val eq 'incorrect_attempted') {$code = '.';} + elsif ($val eq 'incorrect_by_override'){$code = '-';} elsif ($val eq 'excused') {$code = 'x';} + elsif ($val eq 'ungraded_attempted') {$code = '#';} + else {$code = ' ';} $partData{$part.':code'}=$code; } } - # Loop through all the parts for the current problem in the - # correct order and prepare the output - foreach (split(/\:/,$cache->{$sequence.':'.$problemID. - ':parts'})) { - $codes .= $partData{$_.':code'}.','; - $attempts .= $partData{$_.':tries'}.','; - } - } - $codes =~ s/,$//; - $attempts =~ s/,$//; - $Str .= ''; - $Str .= ''; - $Str .= ''."\n"; + # All grades (except for versionless parts) are displayed as links + # to their submission record. Loop through all the parts for the + # current problem in the correct order and prepare the output links + $Str .= ''; + foreach(split(/\:/,$cache->{$sequence.':'.$problemID. + ':parts'})) { + if($partData{$_.':code'} eq '*') { + $problemsCorrect++; + if (($partData{$_.':tries'}<10) && + ($partData{$_.':tries'} ne '')) { + $partData{$_.':code'}=$partData{$_.':tries'}; + } + } elsif($partData{$_.':code'} eq '+') { + $problemsCorrect++; + } + + $Str .= $partData{$_.':code'}; + $characterCount++; + + if($partData{$_.':code'} ne 'x') { + $totalProblems++; + } + } + $Str.=''; + } + + # Output the number of correct answers for the current sequence. + # This part takes up 6 character slots, but is formated right + # justified. + my $spacesNeeded=$cache->{$sequence.':columnWidth'}-$characterCount; + $spacesNeeded -= 3; + $Str .= (' 'x$spacesNeeded); + + my $outputProblemsCorrect = sprintf( "%3d", $problemsCorrect ); + $Str .= ''.$outputProblemsCorrect.''; + $problemsSolved += $problemsCorrect; + $problemsCorrect=0; + + $Str .= $spacing; } - $Str .= '
\# Set Title Results Tries
'.$sequence.''.$cache->{$sequence.':title'}.''.$codes.''.$attempts.'
'."\n"; + # Output the total correct problems over the total number of problems. + # I don't like this type of formatting, but it is a solution. Need + # a way to dynamically determine the space requirements. + my $outputProblemsSolved = sprintf( "%4d", $problemsSolved ); + my $outputTotalProblems = sprintf( "%4d", $totalProblems ); + $Str .= ''.$outputProblemsSolved. + ' / '.$outputTotalProblems.''; 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 = "

".
+              "1..9: correct by student in 1..9 tries\n".
+              "   *: correct by student in more than 9 tries\n".
+	      "   +: correct by override\n".
+              "   -: incorrect by override\n".
+	      "   .: incorrect attempted\n".
+	      "   #: ungraded attempted\n".
+              "    : not attempted\n".
+	      "   x: excused".
+              "

"; + return $Str; +} + +=pod + +=item &CreateColumnSelectionBox() + +If there are columns not being displayed then this selection box is created +with a list of those columns. When selections are made and the page +refreshed, the columns will be removed from this box and the column is +put back in the chart. If there is no columns to select, no row is added +to the interface table. + +=over 4 +Input: $CacheData, $headings + + +$CacheData: A pointer to a hash tied to the cached data + +$headings: An array of the names of the columns for the student information. +They are used for displaying which columns are missing. + +Output: $notThere + +$notThere: The string contains one row of a table. The first column has the +name of the selection box. The second contains the selection box +which has a size of four. + +=back + +=cut + +sub CreateColumnSelectionBox { + my ($CacheData,$headings)=@_; + + my $missing=0; + my $notThere='

Select column to view:'; + my $name; + $notThere .= ''; + $notThere .= ''; + } else { + $notThere='
'; + } + + return $notThere.'
'; + $present .= ''; + $present .= ''; + $present .= ''; + $present .= '