--- loncom/interface/statistics/lonstudentassessment.pm	2005/01/05 20:01:46	1.108
+++ loncom/interface/statistics/lonstudentassessment.pm	2005/02/25 02:37:49	1.112
@@ -1,6 +1,6 @@
 # The LearningOnline Network with CAPA
 #
-# $Id: lonstudentassessment.pm,v 1.108 2005/01/05 20:01:46 matthew Exp $
+# $Id: lonstudentassessment.pm,v 1.112 2005/02/25 02:37:49 matthew Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -56,6 +56,7 @@ use Apache::loncommon();
 use Apache::loncoursedata;
 use Apache::lonnet; # for logging porpoises
 use Apache::lonlocal;
+use Time::HiRes;
 use Spreadsheet::WriteExcel;
 use Spreadsheet::WriteExcel::Utility();
 
@@ -176,6 +177,7 @@ sub BuildStudentAssessmentPage {
     my $initialize     = \&html_initialize;
     my $output_student = \&html_outputstudent;
     my $finish         = \&html_finish;
+    &Apache::lonnet::logthis('got here! 1');
     #
     if ($output_mode eq 'excel') {
         $initialize     = \&excel_initialize;
@@ -187,6 +189,7 @@ sub BuildStudentAssessmentPage {
         $finish         = \&csv_finish;
     }
     #
+    &Apache::lonnet::logthis('got here! 2');
     if($c->aborted()) {  return ; }
     #
     # Determine which students we want to look at
@@ -211,6 +214,7 @@ sub BuildStudentAssessmentPage {
     #
     # Call the initialize routine selected above
     $initialize->($r);
+    &Apache::lonnet::logthis('got here! 3');
     foreach my $student (@Students) {
         if($c->aborted()) { 
             $finish->($r);
@@ -327,21 +331,12 @@ sub CreateInterface {
     $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::lonhtmlcommon::StatusOptions(undef,undef,5);
     $Str .= '</td><td>'."\n";
-    $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
-                                              $only_seq_with_assessments);
+    $Str .= &Apache::lonstatistics::map_select('Maps','multiple,all',5);
     $Str .= '</td><td>'."\n";
     $Str .= &CreateAndParseOutputSelector();
     $Str .= '</td><td>'."\n";
@@ -358,7 +353,7 @@ sub CreateInterface {
         &mt('Clear Caches').'" />';
     $Str .= '&nbsp;'x5;
     $Str .= 
-        &mt('Status [_1]',
+        &mt('Status[_1]',
             '<input type="text" name="stats_status" size="60" value="" />');
     $Str .= '<br />';
     return $Str;
@@ -519,6 +514,7 @@ my @OutputDataOptions =
        maximum_row => 0,
        shortdesc => 'Number of Tries before success on each Problem Part',
        longdesc =>'The number of tries before success on each problem part.',
+       non_html_notes => 'negative values indicate an incorrect problem',
        },
      { name  =>'Parts Correct',
        base  =>'tries',
@@ -612,6 +608,10 @@ Return a line of the chart for a student
     my %prog_state;   # progress state used by loncommon PrgWin routines
     my $total_sum_width;
 
+    my %width; # Holds sequence width information
+    my @sequences;
+    my $navmap; # Have to keep this around since weakref is a bit zealous
+
 sub html_initialize {
     my ($r) = @_;
     #
@@ -619,10 +619,14 @@ sub html_initialize {
     $count = 0;
     $nodata_count = 0;
     undef(%prog_state);
+    undef(%width);
+    undef($navmap);
+    undef(@sequences);
+    &Apache::lonnet::logthis('called html_initialize');
     #
     $r->print("<h3>".$ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
               "&nbsp;&nbsp;".localtime(time)."</h3>");
-
+    #
     if ($chosen_output->{'base'} !~ /^final table/) {
         $r->print("<h3>".$chosen_output->{'shortdesc'}."</h3>");        
     }
@@ -638,45 +642,68 @@ sub html_initialize {
     #
     # Compute the column widths and output the sequence titles
     my $total_count;
-    foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()){
-        #
-        # Comptue column widths
-        $sequence->{'width_sum'} = 0;
+    ($navmap,@sequences) = 
+        &Apache::lonstatistics::sequences_with_assessments();
+    if (! ref($navmap)) {
+        # Unable to get data, so bail out
+        $r->print("<h3>".
+                  &mt('Unable to retrieve course information.').
+                  '</h3>');
+    }
+    #
+    # Compute sequence widths
+    my $starttime = Time::HiRes::time;
+    foreach my $seq (@sequences) {
+        my $symb = $seq->symb;
+        my $title = $seq->compTitle;
+        $width{$symb}->{'width_sum'} = 0;
+        # Compute width of sum
         if ($chosen_output->{'sequence_sum'}) {
             if ($chosen_output->{'every_problem'}) {
                 # Use 1 digit for a space
-                $sequence->{'width_sum'} += 1;            
+                $width{$symb}->{'width_sum'} += 1;            
             }
-	    $total_count += $sequence->{'num_assess_parts'};
+	    $total_count += $width{$symb}->{'num_assess_parts'};
             # Use 3 digits for the sum
-            $sequence->{'width_sum'} += 3;
+            $width{$symb}->{'width_sum'} += 3;
         }
+        # Compute width of maximum
         if ($chosen_output->{'sequence_max'}) {
-            if ($sequence->{'width_sum'}>0) {
+            if ($width{$symb}->{'width_sum'}>0) {
                 # One digit for the '/'
-                $sequence->{'width_sum'} +=1;
+                $width{$symb}->{'width_sum'} +=1;
             }
             # Use 3 digits for the total
-            $sequence->{'width_sum'}+=3;
+            $width{$symb}->{'width_sum'}+=3;
         }
 	#
         if ($chosen_output->{'every_problem'}) {
             # one problem per digit
-            $sequence->{'width_problem'} = $sequence->{'num_assess_parts'};
+            $width{$symb}->{'width_parts'}=0;
+            $starttime = Time::HiRes::time;
+            my @resources = 
+                $navmap->retrieveResources($seq,sub { shift->is_problem(); },
+                                           0,0,0);
+            $starttime = Time::HiRes::time;
+            foreach my $res (@resources) {
+                my @parts = $res->parts;
+                $width{$symb}->{'width_parts'} += scalar(@parts);
+            } 
+            &Apache::lonnet::logthis('2elapsed:'.(Time::HiRes::time-$starttime));            $width{$symb}->{'width_problem'} += 
+                $width{$symb}->{'width_parts'};
         } else {
-            $sequence->{'width_problem'} = 0;
+            $width{$symb}->{'width_problem'} = 0;
         }
-        $sequence->{'width_total'} = $sequence->{'width_problem'} + 
-                                     $sequence->{'width_sum'};
-        if ($sequence->{'width_total'} < length(&HTML::Entities::decode($sequence->{'title'}))) {
-            $sequence->{'width_total'} = length(&HTML::Entities::decode($sequence->{'title'}));
+        $width{$symb}->{'width_total'} = $width{$symb}->{'width_problem'} + 
+                                     $width{$symb}->{'width_sum'};
+        if ($width{$symb}->{'width_total'} < length(&HTML::Entities::decode($title))) {
+            $width{$symb}->{'width_total'} = length(&HTML::Entities::decode($title));
         }
         #
         # Output the sequence titles
-        $Str .= 
-            $sequence->{'title'}.' 'x($sequence->{'width_total'}-
-                                      length($sequence->{'title'})
-                                      ).$padding;
+        $Str .= $title.(' 'x($width{$symb}->{'width_total'}-
+                            length($title)
+                            )).$padding;
     }
     $total_sum_width = length($total_count)+1;
     $Str .= "    total</pre>\n";
@@ -689,6 +716,7 @@ sub html_initialize {
 sub html_outputstudent {
     my ($r,$student) = @_;
     my $Str = '';
+    return if (! defined($navmap));
     #
     if($count++ % 5 == 0 && $count > 0) {
         $r->print("</pre><pre>");
@@ -724,16 +752,17 @@ sub html_outputstudent {
     # By sequence build up the data
     my $studentstats;
     my $PerformanceStr = '';
-    foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
+    foreach my $seq (@sequences) {
+        &Apache::lonnet::logthis('computing student data for '.$seq->compTitle);
         my ($performance,$performance_length,$score,$seq_max,$rawdata);
         if ($chosen_output->{'tries'}) {
             ($performance,$performance_length,$score,$seq_max,$rawdata) =
-                &StudentTriesOnSequence($student,\%StudentsData,
-                                        $seq,$show_links);
+                &student_tries_on_sequence($student,\%StudentsData,
+                                           $navmap,$seq,$show_links);
         } else {
             ($performance,$performance_length,$score,$seq_max,$rawdata) =
-                &StudentPerformanceOnSequence($student,\%StudentsData,
-                                              $seq,$show_links);
+                &student_performance_on_sequence($student,\%StudentsData,
+                                                 $navmap,$seq,$show_links);
         }
         my $ratio='';
         if ($chosen_output->{'every_problem'}) {
@@ -755,13 +784,15 @@ sub html_outputstudent {
             $performance = '';
 	    $performance_length=0;
         }
-        $performance .= ' 'x($seq->{'width_total'}-$performance_length-$seq->{'width_sum'}).
+        $performance .= ' 'x($width{$seq->symb}->{'width_total'} -
+                             $performance_length -
+                             $width{$seq->symb}->{'width_sum'}).
             $ratio;
         #
         $Str .= $performance.$padding;
         #
-        $studentstats->{$seq->{'symb'}}->{'score'}= $score;
-        $studentstats->{$seq->{'symb'}}->{'max'}  = $seq_max;
+        $studentstats->{$seq->symb}->{'score'}= $score;
+        $studentstats->{$seq->symb}->{'max'}  = $seq_max;
     }
     #
     # Total it up and store the statistics info.
@@ -793,6 +824,7 @@ sub html_outputstudent {
 
 sub html_finish {
     my ($r) = @_;
+    return if (! defined($navmap));
     #
     # Check for suppressed output and close the progress window if so
     $r->print("</pre>\n"); 
@@ -804,6 +836,7 @@ sub html_finish {
         }
     }
     $r->rflush();
+    undef($navmap);
     return;
 }
 
@@ -815,19 +848,20 @@ sub StudentAverageTotal {
         '<th>'.&mt('Average').'</th>'.
         '<th>'.&mt('Maximum').'</th>'.
         '</tr>'.$/;
-    foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
+    foreach my $seq (@sequences) {
+        my $symb = $seq->symb;
         my $ave;
-        my $num_students = $Statistics->{$seq->{'symb'}}->{'num_students'};
+        my $num_students = $Statistics->{$symb}->{'num_students'};
         if ($num_students > 0) {
             $ave = int(100*
-                       ($Statistics->{$seq->{'symb'}}->{'score'}/$num_students)
+                       ($Statistics->{$symb}->{'score'}/$num_students)
                        )/100;
         } else {
             $ave = 0;
         }
-        my $max = $Statistics->{$seq->{'symb'}}->{'max'};
+        my $max = $Statistics->{$symb}->{'max'};
         $ave = sprintf("%.2f",$ave);
-        $Str .= '<tr><td>'.$seq->{'title'}.'</td>'.
+        $Str .= '<tr><td>'.$seq->compTitle.'</td>'.
             '<td align="right">'.$ave.'&nbsp;</td>'.
             '<td align="right">'.$max.'&nbsp;'.'</td></tr>'."\n";
     }
@@ -854,9 +888,9 @@ sub SingleStudentTotal {
     my $total = 0;
     my $total_max = 0;
     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
-        my $value = $Statistics->{$seq->{'symb'}}->{'score'};
-        my $max = $Statistics->{$seq->{'symb'}}->{'max'};
-        $Str .= '<tr><td>'.$seq->{'title'}.'</td>'.
+        my $value = $Statistics->{$seq->symb}->{'score'};
+        my $max = $Statistics->{$seq->symb}->{'max'};
+        $Str .= '<tr><td>'.&HTML::Entities::encode($seq->compTitle).'</td>'.
             '<td align="right">'.$value.'</td>'.
                 '<td align="right">'.$max.'</td></tr>'."\n";
         $total += $value;
@@ -954,6 +988,7 @@ sub excel_initialize {
     # Determine rows 
     my $header_row = $rows_output++;
     my $description_row = $rows_output++;
+    my $notes_row = $rows_output++;
     $rows_output++;        # blank row
     my $summary_header_row;
     if ($chosen_output->{'summary_table'}) {
@@ -1028,7 +1063,13 @@ sub excel_initialize {
     $cols_output = 0;
     $excel_sheet->write($description_row,$cols_output++,
                         $chosen_output->{'shortdesc'},
-                        $format->{'h3'});
+                        $format->{'b'});
+    #
+    $cols_output = 0;
+    $excel_sheet->write($notes_row,$cols_output++,
+                        $chosen_output->{'non_html_notes'},
+                        $format->{'i'});
+    
     ##############################################
     # Output headings for the raw data
     ##############################################
@@ -1196,16 +1237,15 @@ sub excel_initialize {
             #
             if ($chosen_output->{'sequence_sum'} && 
                 $chosen_output->{'every_problem'}) {
-                my %replaceCells;
-                $replaceCells{$seq->{'Excel:startcell'}} = 
-                    &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
-                    ($maximum_data_row,$seq->{'Excel:startcol'});
-                $replaceCells{$seq->{'Excel:endcell'}} = 
-                    &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
-                    ($maximum_data_row,$seq->{'Excel:endcol'});
+		my %replaceCells=
+		    ('^'.$seq->{'Excel:startcell'}.':'.
+		         $seq->{'Excel:endcell'}.'$' =>
+		     &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$seq->{'Excel:startcol'}).':'.
+		     &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$seq->{'Excel:endcol'}));
                 $excel_sheet->repeat_formula($maximum_data_row,$cols_output++,
                                              $seq->{'Excel:sum'},undef,
-                                             %replaceCells);
+					     %replaceCells);
+			
             } elsif ($chosen_output->{'sequence_sum'}) {
                 $excel_sheet->write($maximum_data_row,$cols_output++,$max);
             }
@@ -1375,23 +1415,16 @@ sub excel_outputstudent {
         if ($chosen_output->{'sequence_sum'} && 
             $chosen_output->{'every_problem'}) {
             # Write a formula for the sum of this sequence
-            my %replaceCells;
-            $replaceCells{$seq->{'Excel:startcell'}} = 
-                &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
-                            ($rows_output,$seq->{'Excel:startcol'});
-            $replaceCells{$seq->{'Excel:endcell'}} = 
-                &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
-                            ($rows_output,$seq->{'Excel:endcol'});
-            # The undef is for the format
-            if (scalar(keys(%replaceCells)) == 1) {
-                $excel_sheet->repeat_formula($rows_output,$cols_output++,
-                                             $seq->{'Excel:sum'},undef,
-                                             %replaceCells,%replaceCells);
-            } else {
-                $excel_sheet->repeat_formula($rows_output,$cols_output++,
-                                             $seq->{'Excel:sum'},undef,
-                                             %replaceCells);
-            }
+            my %replaceCells=
+		('^'.$seq->{'Excel:startcell'}.':'.$seq->{'Excel:endcell'}.'$'
+		 => 
+		 &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($rows_output,$seq->{'Excel:startcol'}).':'.
+		 &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($rows_output,$seq->{'Excel:endcol'})
+		 );
+            # The undef is for the format	    
+	    $excel_sheet->repeat_formula($rows_output,$cols_output++,
+					 $seq->{'Excel:sum'},undef,
+					 %replaceCells);
         } elsif ($chosen_output->{'sequence_sum'}) {
             if ($score eq ' ') {
                 $cols_output++;
@@ -1516,6 +1549,12 @@ END
     print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'.
         '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'.
             "\n";
+    foreach my $item ('shortdesc','non_html_notes') {
+        next if (! exists($chosen_output->{$item}));
+        print $outputfile 
+            '"'.&Apache::loncommon::csv_translate($chosen_output->{$item}).'"'.
+            "\n";
+    }
     #
     # Print out the headings
     my $sequence_row = '';
@@ -1700,8 +1739,8 @@ Inputs:
 
 #######################################################
 #######################################################
-sub StudentTriesOnSequence {
-    my ($student,$studentdata,$seq,$links) = @_;
+sub student_tries_on_sequence {
+    my ($student,$studentdata,$navmap,$seq,$links) = @_;
     $links = 'no' if (! defined($links));
     my $Str = '';
     my ($sum,$max) = (0,0);
@@ -1709,11 +1748,12 @@ sub StudentTriesOnSequence {
     my @TriesData = ();
     my $tries;
     my $hasdata = 0; # flag - true if the student has any data on the sequence
-    foreach my $resource (@{$seq->{'contents'}}) {
-        next if ($resource->{'type'} ne 'assessment');
-        my $resource_data = $studentdata->{$resource->{'symb'}};
+    my @resources = 
+        $navmap->retrieveResources($seq,sub { shift->is_problem(); },0,0,0);
+    foreach my $resource (@resources) {
+        my $resource_data = $studentdata->{$resource->symb};
         my $value = '';
-        foreach my $partnum (@{$resource->{'parts'}}) {
+        foreach my $partnum (@{$resource->parts()}) {
             $tries = undef;
             $max++;
             $performance_length++;
@@ -1773,10 +1813,16 @@ sub StudentTriesOnSequence {
                 }
             }
             #
-            if (! defined($tries) || $symbol eq '.') {
-                $tries = $symbol;
+            if (! defined($tries)) {
+                $tries = 0;
+            }
+            if ($status =~ /^(incorrect|ungraded)/) {
+                # Bug 3390: show '-' for tries on incorrect problems 
+                # (csv & excel only)
+                push(@TriesData,-$tries);
+            } else {
+                push (@TriesData,$tries);
             }
-            push (@TriesData,$tries);
             #
             if ( ($links eq 'yes' && $symbol ne ' ') ||
                  ($links eq 'all')) {
@@ -1784,7 +1830,7 @@ sub StudentTriesOnSequence {
                     &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1');
                 }
                 $symbol = '<a href="/adm/grades'.
-                    '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
+                    '?symb='.&Apache::lonnet::escape($resource->symb).
                         '&student='.$student->{'username'}.
                             '&userdom='.$student->{'domain'}.
                                 '&command=submission">'.$symbol.'</a>';
@@ -1793,8 +1839,8 @@ sub StudentTriesOnSequence {
         }
         $Str .= $value;
     }
-    if ($seq->{'randompick'}) {
-        $max = $seq->{'randompick'};
+    if ($seq->randompick()) {
+        $max = $seq->randompick();
     }
     if (! $hasdata && $sum == 0) {
         $sum = ' ';
@@ -1827,8 +1873,8 @@ Inputs:
 
 #######################################################
 #######################################################
-sub StudentPerformanceOnSequence {
-    my ($student,$studentdata,$seq,$links) = @_;
+sub student_performance_on_sequence {
+    my ($student,$studentdata,$navmap,$seq,$links) = @_;
     $links = 'no' if (! defined($links));
     my $Str = ''; # final result string
     my ($score,$max) = (0,0);
@@ -1837,13 +1883,15 @@ sub StudentPerformanceOnSequence {
     my @ScoreData = ();
     my $partscore;
     my $hasdata = 0; # flag, 0 if there were no submissions on the sequence
-    foreach my $resource (@{$seq->{'contents'}}) {
-        next if ($resource->{'type'} ne 'assessment');
-        my $resource_data = $studentdata->{$resource->{'symb'}};
-        foreach my $part (@{$resource->{'parts'}}) {
+    my @resources = 
+        $navmap->retrieveResources($seq,sub { shift->is_problem(); },0,0,0);
+    foreach my $resource (@resources) {
+        my $symb = $resource->symb;
+        my $resource_data = $studentdata->{$symb};
+        foreach my $part (@{$resource->parts()}) {
             $partscore = undef;
             my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
-                                              $resource->{'symb'},
+                                              $symb,
                                               $student->{'domain'},
                                               $student->{'username'},
                                               $student->{'section'});
@@ -1881,7 +1929,7 @@ sub StudentPerformanceOnSequence {
                     $max -= $weight; # Do not count 'excused' problems.
                 }
                 $hasdata = 1;
-            } else {
+            } elsif (!exists($resource_data->{'resource.'.$part.'.awarded'})){
                 # Unsolved.  Did they try?
                 if (exists($resource_data->{'resource.'.$part.'.tries'})){
                     $symbol = '.';
@@ -1898,7 +1946,7 @@ sub StudentPerformanceOnSequence {
             #
             if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) {
                 $symbol = '<a href="/adm/grades'.
-                    '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
+                    '?symb='.&Apache::lonnet::escape($symb).
                     '&student='.$student->{'username'}.
                     '&userdom='.$student->{'domain'}.
                     '&command=submission">'.$symbol.'</a>';