--- loncom/interface/statistics/lonstudentassessment.pm 2004/12/10 16:52:53 1.107 +++ loncom/interface/statistics/lonstudentassessment.pm 2005/02/25 19:48:01 1.114 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonstudentassessment.pm,v 1.107 2004/12/10 16:52:53 matthew Exp $ +# $Id: lonstudentassessment.pm,v 1.114 2005/02/25 19:48:01 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 .= ''."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); $Str .= ''; - 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 .= ''."\n"; $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); $Str .= ''."\n"; - $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5, - $only_seq_with_assessments); + $Str .= &Apache::lonstatistics::map_select('Maps','multiple,all',5); $Str .= ''."\n"; $Str .= &CreateAndParseOutputSelector(); $Str .= ''."\n"; @@ -357,6 +352,9 @@ sub CreateInterface { $Str .= ''; $Str .= ' 'x5; + $Str .= + &mt('Status[_1]', + ''); $Str .= '
'; return $Str; } @@ -516,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', @@ -609,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) = @_; # @@ -616,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("

".$ENV{'course.'.$ENV{'request.course.id'}.'.description'}. "  ".localtime(time)."

"); - + # if ($chosen_output->{'base'} !~ /^final table/) { $r->print("

".$chosen_output->{'shortdesc'}."

"); } @@ -635,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::selected_sequences_with_assessments(); + if (! ref($navmap)) { + # Unable to get data, so bail out + $r->print("

". + &mt('Unable to retrieve course information.'). + '

'); + } + # + # 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\n"; @@ -686,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("
");
@@ -721,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'}) {
@@ -752,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.
@@ -790,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("
\n"); @@ -801,6 +836,7 @@ sub html_finish { } } $r->rflush(); + undef($navmap); return; } @@ -812,19 +848,20 @@ sub StudentAverageTotal { ''.&mt('Average').''. ''.&mt('Maximum').''. ''.$/; - 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 .= ''.$seq->{'title'}.''. + $Str .= ''.$seq->compTitle.''. ''.$ave.' '. ''.$max.' '.''."\n"; } @@ -851,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 .= ''.$seq->{'title'}.''. + my $value = $Statistics->{$seq->symb}->{'score'}; + my $max = $Statistics->{$seq->symb}->{'max'}; + $Str .= ''.&HTML::Entities::encode($seq->compTitle).''. ''.$value.''. ''.$max.''."\n"; $total += $value; @@ -951,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'}) { @@ -1025,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 ############################################## @@ -1193,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); } @@ -1291,7 +1334,8 @@ sub excel_initialize { # Initialize progress window %prog_state=&Apache::lonhtmlcommon::Create_PrgWin ($r,'Excel File Compilation Status', - 'Excel File Compilation Progress', $studentcount); + 'Excel File Compilation Progress', $studentcount, + 'inline',undef,'Statistics','stats_status'); # &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, 'Processing first student'); @@ -1371,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++; @@ -1492,7 +1529,8 @@ END my $studentcount = scalar(@Apache::lonstatistics::Students); %prog_state=&Apache::lonhtmlcommon::Create_PrgWin ($r,'CSV File Compilation Status', - 'CSV File Compilation Progress', $studentcount); + 'CSV File Compilation Progress', $studentcount, + 'inline',undef,'Statistics','stats_status'); # # Open a file $filename = '/prtspool/'. @@ -1511,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 = ''; @@ -1695,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); @@ -1704,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++; @@ -1768,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')) { @@ -1779,7 +1830,7 @@ sub StudentTriesOnSequence { &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1'); } $symbol = ''.$symbol.''; @@ -1788,8 +1839,8 @@ sub StudentTriesOnSequence { } $Str .= $value; } - if ($seq->{'randompick'}) { - $max = $seq->{'randompick'}; + if ($seq->randompick()) { + $max = $seq->randompick(); } if (! $hasdata && $sum == 0) { $sum = ' '; @@ -1822,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); @@ -1832,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'}); @@ -1876,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 = '.'; @@ -1893,7 +1946,7 @@ sub StudentPerformanceOnSequence { # if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) { $symbol = ''.$symbol.'';