--- loncom/interface/statistics/lonstudentassessment.pm 2003/03/03 19:28:29 1.31 +++ loncom/interface/statistics/lonstudentassessment.pm 2003/06/05 19:21:22 1.52 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonstudentassessment.pm,v 1.31 2003/03/03 19:28:29 matthew Exp $ +# $Id: lonstudentassessment.pm,v 1.52 2003/06/05 19:21:22 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -83,7 +83,10 @@ my $Statistics; =item $output_mode 'html', 'excel', or 'csv' for output mode -=item $show 'all' or 'totals' determines how much data is output +=item $show 'all', 'totals', or 'scores' determines how much data is output + +=item $single_student_mode evaluates to true if we are showing only one +student. =cut @@ -92,6 +95,7 @@ my $Statistics; my $show_links; my $output_mode; my $show; +my $single_student_mode; ####################################################### ####################################################### @@ -127,22 +131,26 @@ Inputs: sub BuildStudentAssessmentPage { my ($r,$c)=@_; undef($Statistics); + $single_student_mode = 1 if ($ENV{'form.SelectedStudent'}); # # Print out the HTML headers for the interface # This also parses the output mode selector # This step must always be done. $r->print(&CreateInterface()); $r->print(''); + $r->print(''); $r->rflush(); - if (! exists($ENV{'form.notfirstrun'})) { + if (! exists($ENV{'form.notfirstrun'}) && ! $single_student_mode) { $r->print(< - + Please make your selections in the boxes above and hit the button marked "Update Display".

ENDMSG +# $r->print(&OutputDescriptions()); return; } # @@ -155,6 +163,10 @@ ENDMSG $initialize = \&excel_initialize; $output_student = \&excel_outputstudent; $finish = \&excel_finish; +# } elsif ($output_mode eq 'multi-sheet excel') { +# $initialize = \&multi_sheet_excel_initialize; +# $output_student = \&multi_sheet_excel_outputstudent; +# $finish = \&multi_sheet_excel_finish; } elsif ($output_mode eq 'csv') { $initialize = \&csv_initialize; $output_student = \&csv_outputstudent; @@ -163,9 +175,19 @@ ENDMSG # if($c->aborted()) { return ; } # + # Determine which students we want to look at + my @Students; + if ($single_student_mode) { + @Students = (&Apache::lonstatistics::current_student()); + $r->print(&next_and_previous_buttons()); + $r->rflush(); + } else { + @Students = @Apache::lonstatistics::Students; + } + # # Call the initialize routine selected above $initialize->($r); - foreach my $student (@Apache::lonstatistics::Students) { + foreach my $student (@Students) { if($c->aborted()) { $finish->($r); return ; @@ -181,6 +203,54 @@ ENDMSG ####################################################### ####################################################### +sub next_and_previous_buttons { + my $Str = ''; + $Str .= ''; + # + # Build the previous student link + my $previous = &Apache::lonstatistics::previous_student(); + my $previousbutton = ''; + if (defined($previous)) { + my $sname = $previous->{'username'}.':'.$previous->{'domain'}; + $previousbutton .= ''; + } else { + $previousbutton .= ''; + } + # + # Build the next student link + my $next = &Apache::lonstatistics::next_student(); + my $nextbutton = ''; + if (defined($next)) { + my $sname = $next->{'username'}.':'.$next->{'domain'}; + $nextbutton .= ''; + } else { + $nextbutton .= ''; + } + # + # Build the 'all students' button + my $all = ''; + $all .= ''; + $Str .= $previousbutton.(' 'x5).$all.(' 'x5).$nextbutton; + return $Str; +} + +####################################################### +####################################################### sub get_student_fields_to_show { my @to_show = @Apache::lonstatistics::SelectedStudentData; @@ -193,19 +263,6 @@ sub get_student_fields_to_show { return @to_show; } -sub get_sequences_to_show { - my @Sequences; - 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); - push (@Sequences,$sequence); - } - } - return @Sequences; -} - - ####################################################### ####################################################### @@ -232,6 +289,7 @@ sub CreateInterface { $Str .= ''; $Str .= 'Sections'; $Str .= 'Student Data'; + $Str .= 'Enrollment Status'; $Str .= 'Sequences and Folders'; $Str .= 'Output Format'; $Str .= ''."\n"; @@ -250,6 +308,8 @@ sub CreateInterface { $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 .= ''."\n"; @@ -270,15 +330,138 @@ sub CreateInterface { ####################################################### ####################################################### +my @OutputOptions = + ({ name => 'HTML, with links', + value => 'html, with links', + description => 'Output HTML with each symbol linked to the problem '. + 'which generated it.', + mode => 'html', + show => 'all', + show_links => 'yes', + }, + { name => 'HTML, with all links', + value => 'html, with all links', + description => 'Output HTML with each symbol linked to the problem '. + 'which generated it. '. + 'This includes links for unattempted problems.', + mode => 'html', + show => 'all', + show_links => 'all', + }, + { name => 'HTML, without links', + value => 'html, without links', + description => 'Output HTML. By not including links, the size of the'. + ' web page is greatly reduced. If your browser crashes on the '. + 'full display, try this.', + mode => 'html', + show => 'all', + show_links => 'no', + }, + { name => 'HTML, scores only', + value => 'html, scores only', + description => 'Output HTML, only showing the total number of correct'. + ' problems (or problem parts) and not the maximum possible for '. + 'each student', + mode => 'html', + show => 'scores', + show_links => 'no', + }, + { name => 'HTML, totals', + value => 'html, totals', + description => 'Output HTML, but only the summary statistics for each'. + ' sequence selected for each student.', + mode => 'html', + show => 'totals', + show_links => 'no', + }, + { name => 'HTML, summary table only', + value => 'html summary table only', + description => 'Output HTML, but only the final summary table for '. + 'all students across all sequences.', + mode => 'html', + show => 'final table', + show_links => 'no', + }, + { name => 'Excel, scores only', + value => 'excel, scores only', + description => 'Output an Excel file (compatable with Excel 95), '. + 'with a single column for each sequence showing the students '. + 'score.', + mode => 'excel', + show => 'scores', + show_links => 'no', + }, + { name => 'Excel, totals', + value => 'excel, totals', + description => 'Output an Excel file (compatable with Excel 95), '. + 'with two columns for each sequence, the students score on the '. + 'sequence and the students maximum possible on the sequence', + mode => 'excel', + show => 'totals', + show_links => 'no', + }, +# { name => 'multi-sheet Excel', +# value => 'multi-sheet excel', +# description => 'Output an Excel file (compatable with Excel 95), '. +# 'with a seperate worksheet for each sequence you have selected '. +# 'the data for each problem part '. +# '(number of tries, status, points awarded) will be listed.', +# mode => 'multi-sheet excel', +# show => 'totals', +# show_links => 'no', +# }, +# { name => 'multi-sheet Excel, by section', +# value => 'multi-sheet excel, by section', +# description => 'Output an Excel file (compatable with Excel 95), '. +# 'with a seperate worksheet for each sequence you have selected '. +# 'the data for each problem part '. +# '(number of tries, status, points awarded) will be listed. '. +# 'There will be one Excel workbook for each section selected.', +# mode => 'multi-sheet excel', +# show => 'by section', +# show_links => 'no', +# }, + { name => 'CSV, everything', + value => 'csv, everything', + description => '', + mode => 'csv', + show => 'all', + show_links => 'no', + }, + { name => 'CSV, scores only', + value => 'csv, scores only', + description => '', + mode => 'csv', + show => 'scores', + show_links => 'no', + }, + { name => 'CSV, totals', + value => 'csv, totals', + description => '', + mode => 'csv', + show => 'totals', + show_links => 'no', + }, + ); + +sub OutputDescriptions { + my $Str = ''; + $Str .= "

Output Modes

\n"; + $Str .= "
\n"; + foreach my $outputmode (@OutputOptions) { + $Str .="
".$outputmode->{'name'}."
\n"; + $Str .="
".$outputmode->{'description'}."
\n"; + } + $Str .= "
\n"; + return $Str; +} + sub CreateAndParseOutputSelector { my $Str = ''; - my $elementname = 'outputmode'; + my $elementname = 'chartoutputmode'; # # Format for output options is 'mode, restrictions'; - my @Options = ('html, with links','html, without links', - 'html, totals only','excel, totals only', - 'csv, totals only','csv, everything'); - my $selected = 'html, with links'; + my $selected = 'html, without links'; if (exists($ENV{'form.'.$elementname})) { if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) { $selected = $ENV{'form.'.$elementname}->[0]; @@ -291,30 +474,20 @@ sub CreateAndParseOutputSelector { $show_links = 'no'; $output_mode = 'html'; $show = 'all'; - my ($mode,$restriction) = split(',',$selected); - $restriction =~ s/^\s*//; - if ($mode =~ /^(html|excel|csv)$/) { - $output_mode = $mode; - } else { - $output_mode = 'html'; - } - if ($restriction eq 'with links') { - $show_links = 'yes'; - } else { - $show_links = 'no'; - } - if ($restriction eq 'totals only') { - $show = 'totals'; - } else { - $show = 'everything'; + foreach my $option (@OutputOptions) { + next if ($option->{'value'} ne $selected); + $output_mode = $option->{'mode'}; + $show = $option->{'show'}; + $show_links = $option->{'show_links'}; } + # # Build the form element $Str = qq/"; return $Str; @@ -345,12 +518,27 @@ Return a line of the chart for a student my $padding; my $count; + my $nodata_count; # The number of students for which there is no data + my %prog_state; # progress state used by loncommon PrgWin routines + sub html_initialize { my ($r) = @_; # $padding = ' 'x3; - $count = 1; + $count = 0; + $nodata_count = 0; + # + $r->print("

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

"); + # + # Set up progress window for 'final table' display only + if ($show eq 'final table') { + my $studentcount = scalar(@Apache::lonstatistics::Students); + %prog_state=&Apache::lonhtmlcommon::Create_PrgWin + ($r,'Summary Table Status', + 'Summary Table Compilation Progress', $studentcount); + } my $Str = "
\n";
     # First, the @StudentData fields need to be listed
     my @to_show = &get_student_fields_to_show();
@@ -361,7 +549,7 @@ sub html_initialize {
         $Str .= $title.' 'x($width-$base).$padding;
     }
     # Now the selected sequences need to be listed
-    foreach my $sequence (&get_sequences_to_show) {
+    foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()){
         my $title = $sequence->{'title'};
         my $base  = $sequence->{'base_width'};
         my $width = $sequence->{'width'};
@@ -369,6 +557,11 @@ sub html_initialize {
     }
     $Str .= "total (of shown problems)
\n"; $Str .= "
";
+    #
+    # Check for suppression of output
+    if ($show eq 'final table') {
+        $Str = '';
+    }
     $r->print($Str);
     $r->rflush();
     return;
@@ -377,6 +570,10 @@ sub html_initialize {
 sub html_outputstudent {
     my ($r,$student) = @_;
     my $Str = '';
+    #
+    if($count++ % 5 == 0 && $count > 0) {
+        $r->print("
");
+    }
     # First, the @StudentData fields need to be listed
     my @to_show = &get_student_fields_to_show();
     foreach my $field (@to_show) {
@@ -394,6 +591,8 @@ sub html_outputstudent {
         %StudentsData = @tmp;
     }
     if (scalar(@tmp) < 1) {
+        $nodata_count++;
+        return if ($show eq 'final table');
         $Str .= 'No Course Data'."\n";
         $r->print($Str);
         $r->rflush();
@@ -403,8 +602,8 @@ sub html_outputstudent {
     # By sequence build up the data
     my $studentstats;
     my $PerformanceStr = '';
-    foreach my $seq (&get_sequences_to_show) {
-        my ($performance,$score,$seq_max) =
+    foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
+        my ($performance,$performance_length,$score,$seq_max) =
             &StudentPerformanceOnSequence($student,\%StudentsData,
                                           $seq,$show_links);
         my $ratio = $score.'/'.$seq_max;
@@ -412,9 +611,12 @@ sub html_outputstudent {
         if ($show eq 'totals') {
             $performance = ' 'x(length($seq_max)-length($score)).$ratio;
             $performance .= ' 'x($seq->{'width'}-length($performance));
+        } elsif ($show eq 'scores') {
+            $performance = $score;
+            $performance .= ' 'x($seq->{'width'}-length($performance));
         } else {
             # Pad with extra spaces
-            $performance .= ' 'x($seq->{'width'}-$seq_max-
+            $performance .= ' 'x($seq->{'width'}-$performance_length-
                                  length($ratio)
                                  ).$ratio;
         }
@@ -435,24 +637,101 @@ sub html_outputstudent {
     }
     $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
     $Str .= " \n";
-    $r->print($Str);
     #
-    $count++;
-    if($count % 5 == 0) {
-        $r->print("
");
+    # Check for suppressed output and update the progress window if so...
+    if ($show eq 'final table') {
+        $Str = '';
+        &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
+                                                 'last student');
     }
     #
+    $r->print($Str);
+    #
     $r->rflush();
     return;
 }    
 
 sub html_finish {
     my ($r) = @_;
-    $r->print("
\n"); + # + # Check for suppressed output and close the progress window if so + if ($show eq 'final table') { + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + } else { + $r->print("\n"); + } + if ($single_student_mode) { + $r->print(&SingleStudentTotal()); + } else { + $r->print(&StudentAverageTotal()); + } $r->rflush(); return; } +sub StudentAverageTotal { + my $Str = "

Summary Tables

\n"; + my $num_students = scalar(@Apache::lonstatistics::Students); + my $total_ave = 0; + my $total_max = 0; + $Str .= ''."\n"; + $Str .= "\n"; + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + my $ave; + if ($num_students > $nodata_count) { + $ave = int(100*($Statistics->{$seq->{'symb'}}->{'score'}/ + ($num_students-$nodata_count)))/100; + } else { + $ave = 0; + } + $total_ave += $ave; + my $max = $seq->{'num_assess_parts'}; + $total_max += $max; + if ($ave == 0) { + $ave = "0.00"; + } + $ave .= ' '; + $max .= '   '; + $Str .= ''. + ''. + ''."\n"; + } + $total_ave = int(100*$total_ave)/100; # only two digit + $Str .= "
TitleAverageMaximum
'.$seq->{'title'}.''.$ave.''.$max.'
\n"; + $Str .= ''."\n"; + $Str .= ''. + "\n"; + $Str .= ''. + ''; + $Str .= "
Number of StudentsAverageMaximum
'.($num_students-$nodata_count).''.$total_ave.''.$total_max.'
\n"; + return $Str; +} + +sub SingleStudentTotal { + my $student = &Apache::lonstatistics::current_student(); + my $Str = "

Summary table for ".$student->{'fullname'}." ". + $student->{'username'}.'@'.$student->{'domain'}."

\n"; + $Str .= ''."\n"; + $Str .= + "\n"; + 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 .= ''. + ''. + ''."\n"; + $total += $value; + $total_max +=$max; + } + $Str .= ''. + ''. + '\n"; + $Str .= "
Sequence or FolderScoreMaximum
'.$seq->{'title'}.''.$value.''.$max.'
Total'.$total.''.$total_max."
\n"; + return $Str; +} + } ####################################################### @@ -460,6 +739,78 @@ sub html_finish { =pod +=head2 Multi-Sheet EXCEL subroutines + +=item &multi_sheet_excel_initialize($r) + +=item &multi_sheet_excel_outputstudent($r,$student) + +=item &multi_sheet_excel_finish($r) + +=cut + +####################################################### +####################################################### +{ + +sub multi_sheet_excel_initialize { + my ($r)=@_; + $r->print("

Not yet implemented

"); + # + # Estimate the size of the file. We would like to have < 5 megs of data. + my $max_size = 5000000; + my $num_students = scalar(@Apache::lonstatistics::Students); + my $num_sequences = 0; + my $num_data_per_part = 2; # 'status' and 'numtries' + my $fields_per_student = scalar(&get_student_fields_to_show()); + my $bytes_per_field = 20; # Back of the envelope calculation + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + $num_sequences++ if ($seq->{'num_assess'} > 0); + $fields_per_student += $num_data_per_part * $seq->{'num_assess_parts'}; + } + my $size_estimate = $fields_per_student*$num_students*$bytes_per_field; + # + # Compute number of workbooks + my $num_workbooks = 1; + if ($size_estimate > $max_size) { # try to stay under 5 megs + $num_workbooks += int($size_estimate / $max_size); + } + if ($show eq 'by section') { + if (@Apache::lonstatistics::SelectedSections > 1 && + $Apache::lonstatistics::SelectedSections[0] ne 'all') { + $num_workbooks = scalar(@Apache::lonstatistics::SelectedSections); + } else { + # @Apache::lonstatistics::Sections contains 'all' as well. + $num_workbooks = scalar(@Apache::lonstatistics::Sections) - 1; + } + } + + $r->print("Maximum allowed size: ".$max_size." bytes
"); + $r->print("Number of students: ".$num_students."
"); + $r->print("Number of fields per student: ".$fields_per_student."
"); + $r->print("Total number of fields: ".($fields_per_student*$num_students). + "
"); + $r->print("Bytes per field: ".$bytes_per_field." (estimated)"."
"); + $r->print("Estimated size: ".$size_estimate." bytes
"); + $r->print("Number of workbooks: ".$num_workbooks."
"); + $r->rflush(); + return; +} + +sub multi_sheet_excel_outputstudent { + my ($r,$student) = @_; +} + +sub multi_sheet_excel_finish { + my ($r) = @_; +} + +} +####################################################### +####################################################### + +=pod + =head2 EXCEL subroutines =item &excel_initialize($r) @@ -475,42 +826,205 @@ sub html_finish { { my $excel_sheet; +my $excel_workbook; + +my $filename; +my $rows_output; +my $cols_output; + +my %prog_state; # progress window state sub excel_initialize { my ($r) = @_; # - $r->print("

Not implemented yet

"); - return; - my $filename = '/prtspool/'. + $filename = '/prtspool/'. $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. time.'_'.rand(1000000000).'.xls'; - $excel_sheet = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); - if (! defined($excel_sheet)) { + # + $excel_workbook = undef; + $excel_sheet = undef; + # + $rows_output = 0; + $cols_output = 0; + # + # Create sheet + $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); + # + # Check for errors + if (! defined($excel_workbook)) { $r->log_error("Error creating excel spreadsheet $filename: $!"); $r->print("Problems creating new Excel file. ". "This error has been logged. ". "Please alert your LON-CAPA administrator"); - return 0; + return ; } # # The excel spreadsheet stores temporary data in files, then put them # together. If needed we should be able to disable this (memory only). # The temporary directory must be specified before calling 'addworksheet'. # File::Temp is used to determine the temporary directory. - $excel_sheet->set_tempdir($Apache::lonnet::tmpdir); + $excel_workbook->set_tempdir($Apache::lonnet::tmpdir); + # + # Add a worksheet + my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; + if (length($sheetname) > 31) { + $sheetname = substr($sheetname,0,31); + } + $excel_sheet = $excel_workbook->addworksheet($sheetname); + # + # Put the course description in the header + $excel_sheet->write($rows_output,$cols_output++, + $ENV{'course.'.$ENV{'request.course.id'}.'.description'}); + $cols_output += 3; + # + # Put a description of the sections listed + my $sectionstring = ''; + my @Sections = @Apache::lonstatistics::SelectedSections; + if (scalar(@Sections) > 1) { + if (scalar(@Sections) > 2) { + my $last = pop(@Sections); + $sectionstring = "Sections ".join(', ',@Sections).', and '.$last; + } else { + $sectionstring = "Sections ".join(' and ',@Sections); + } + } else { + if ($Sections[0] eq 'all') { + $sectionstring = "All sections"; + } else { + $sectionstring = "Section ".$Sections[0]; + } + } + $excel_sheet->write($rows_output,$cols_output++,$sectionstring); + $cols_output += scalar(@Sections); + # + # Put the date in there too + $excel_sheet->write($rows_output,$cols_output++, + 'Compiled on '.localtime(time)); + # + $rows_output++; + # + # Add the student headers + $cols_output = 0; + foreach my $field (&get_student_fields_to_show()) { + $excel_sheet->write($rows_output,$cols_output++,$field); + } + # + # Add the Sequence Headers + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + $excel_sheet->write($rows_output,$cols_output,$seq->{'title'}); + if ($show eq 'totals') { + $excel_sheet->write($rows_output+1,$cols_output,'score'); + $excel_sheet->write($rows_output+1,$cols_output+1,'maximum'); + $cols_output += 2; + } else { + $cols_output++; + } + } + # + # Bookkeeping + if ($show eq 'totals') { + $rows_output += 2; + } else { + $rows_output += 1; + } + # + # Output a row for MAX + if ($show ne 'totals') { + $cols_output = 0; + foreach my $field (&get_student_fields_to_show()) { + if ($field eq 'username' || $field eq 'fullname' || + $field eq 'id') { + $excel_sheet->write($rows_output,$cols_output++,'Maximum'); + } else { + $excel_sheet->write($rows_output,$cols_output++,''); + } + } + # + # Add the Sequence Headers + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + $excel_sheet->write($rows_output,$cols_output++, + $seq->{'num_assess_parts'}); + } + $rows_output++; + } + # + # Let the user know what we are doing + my $studentcount = scalar(@Apache::lonstatistics::Students); + $r->print("

Compiling Excel spreadsheet for ". + $studentcount.' student'); + $r->print('s') if ($studentcount > 1); + $r->print("

\n"); + $r->rflush(); + # + # Initialize progress window + %prog_state=&Apache::lonhtmlcommon::Create_PrgWin + ($r,'Excel File Compilation Status', + 'Excel File Compilation Progress', $studentcount); # - # Determine the name to give the worksheet -# $excel_sheet->addworksheet(); - return; } sub excel_outputstudent { my ($r,$student) = @_; + return if (! defined($excel_sheet)); + $cols_output=0; + # + # Write out student data + my @to_show = &get_student_fields_to_show(); + foreach my $field (@to_show) { + $excel_sheet->write($rows_output,$cols_output++,$student->{$field}); + } + # + # Get student assessment 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; + } + # + # Write out sequence scores and totals data + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + my ($performance,$performance_length,$score,$seq_max) = + &StudentPerformanceOnSequence($student,\%StudentsData, + $seq,'no'); + if ($show eq 'totals' || $show eq 'scores') { + $excel_sheet->write($rows_output,$cols_output++,$score); + } + if ($show eq 'totals') { + $excel_sheet->write($rows_output,$cols_output++,$seq_max); + } + } + # + # Bookkeeping + $rows_output++; + $cols_output=0; + # + # Update the progress window + &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); + return; } sub excel_finish { my ($r) = @_; + return if (! defined($excel_sheet)); + # + # Write the excel file + $excel_workbook->close(); + my $c = $r->connection(); + # + return if($c->aborted()); + # + # Close the progress window + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + # + # Tell the user where to get their excel file + $r->print('
'. + 'Your Excel spreadsheet.'."\n"); + $r->rflush(); + return; } } @@ -533,18 +1047,144 @@ sub excel_finish { ####################################################### { +my $outputfile; +my $filename; + +my %prog_state; # progress window state + sub csv_initialize{ my ($r) = @_; - $r->print("

Not implemented yet

"); + # + # Clean up + $filename = undef; + $outputfile = undef; + undef(%prog_state); + # + # Open a file + $filename = '/prtspool/'. + $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. + time.'_'.rand(1000000000).'.csv'; + unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) { + $r->log_error("Couldn't open $filename for output $!"); + $r->print("Problems occured in writing the csv file. ". + "This error has been logged. ". + "Please alert your LON-CAPA administrator."); + $outputfile = undef; + } + # + # Datestamp + my $description = $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; + print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'. + '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'. + "\n"; + + # + # Print out the headings + my $Str = ''; + my $Str2 = undef; + foreach my $field (&get_student_fields_to_show()) { + if ($show eq 'scores') { + $Str .= '"'.&Apache::loncommon::csv_translate($field).'",'; + } elsif ($show eq 'totals') { + $Str .= '"",'; # first row empty on the student fields + $Str2 .= '"'.&Apache::loncommon::csv_translate($field).'",'; + } elsif ($show eq 'all') { + $Str .= '"'.&Apache::loncommon::csv_translate($field).'",'; + } + } + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + if ($show eq 'scores') { + $Str .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}). + '",'; + } elsif ($show eq 'totals') { + $Str .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}). + '","",'; + $Str2 .= '"score","total possible",'; + } elsif ($show eq 'all') { + $Str .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}). + '",'; + $Str .= '"",'x($seq->{'num_assess_parts'}-1); + $Str .= '"score","total possible",'; + } + } + chop($Str); + $Str .= "\n"; + print $outputfile $Str; + if (defined($Str2)) { + chop($Str2); + $Str2 .= "\n"; + print $outputfile $Str2; + } + # + # Initialize progress window + my $studentcount = scalar(@Apache::lonstatistics::Students); + %prog_state=&Apache::lonhtmlcommon::Create_PrgWin + ($r,'CSV File Compilation Status', + 'CSV File Compilation Progress', $studentcount); return; } sub csv_outputstudent { my ($r,$student) = @_; + return if (! defined($outputfile)); + my $Str = ''; + # + # Output student fields + my @to_show = &get_student_fields_to_show(); + foreach my $field (@to_show) { + $Str .= '"'.&Apache::loncommon::csv_translate($student->{$field}).'",'; + } + # + # Get student assessment 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; + } + # + # Output performance data + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + my ($performance,$performance_length,$score,$seq_max) = + &StudentPerformanceOnSequence($student,\%StudentsData, + $seq,'no'); + if ($show eq 'scores') { + $Str .= '"'.$score.'",'; + } elsif ($show eq 'totals') { + $Str .= '"'.$score.'","'.$seq_max.'",'; + } elsif ($show eq 'all') { + $Str .= '"'.join('","',(split(//,$performance),$score,$seq_max)). + '",'; + } + } + chop($Str); + $Str .= "\n"; + print $outputfile $Str; + # + # Update the progress window + &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); + return; } sub csv_finish { my ($r) = @_; + return if (! defined($outputfile)); + close($outputfile); + # + my $c = $r->connection(); + return if ($c->aborted()); + # + # Close the progress window + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + # + # Tell the user where to get their csv file + $r->print('
'. + 'Your csv file.'."\n"); + $r->rflush(); + return; + } } @@ -575,17 +1215,18 @@ Inputs: ####################################################### ####################################################### sub StudentPerformanceOnSequence { - my ($student,$studentdata,$seq,$links,$totalonly) = @_; - $totalonly = 0 if (! defined($totalonly)); + my ($student,$studentdata,$seq,$links) = @_; $links = 'no' if (! defined($links)); my $Str = ''; my ($sum,$max) = (0,0); + my $performance_length = 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++; + $performance_length++; my $symbol = ' '; # default to space # if (exists($resource_data->{'resource.'.$partnum.'.solved'})) { @@ -613,6 +1254,9 @@ sub StudentPerformanceOnSequence { $symbol = ' '; } $sum++; + } elsif (exists($resource_data->{'resource.'. + $partnum.'.tries'})){ + $symbol = '.'; } else { $symbol = ' '; } @@ -624,8 +1268,15 @@ sub StudentPerformanceOnSequence { $symbol = ' '; } } +# if ($symbol ne ' ') { +# $attempted_sum++; +# } # - if ($links eq 'yes' && $symbol ne ' ') { + if ( ($links eq 'yes' && $symbol ne ' ') || + ($links eq 'all')) { + if (length($symbol) > 1) { + &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1'); + } $symbol = 'Summary Tables:\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'}; - } - $ProblemsSolved += $cache->{$name.':problemsSolved'}; - $TotalProblems += $cache->{$name.':totalProblems'}; - } - if ($StudentCount) { - $ProblemsSolved = sprintf( "%.2f", - $ProblemsSolved/$StudentCount); - $TotalProblems /= $StudentCount; - } else { - $ProblemsSolved = 0; - $TotalProblems = 0; + if ($seq->{'randompick'}) { + $max = $seq->{'randompick'}; } - - $Str .= ''."\n"; - $Str .= ''."\n"; - $Str .= ''."\n"; - $Str .= ''."\n"; - $Str .= '
Students Count'. - $StudentCount.'
Total Problems'. - $TotalProblems.'
Average Correct'. - $ProblemsSolved.'
'."\n"; - - $Str .= ''."\n"; - $Str .= ''. - ''."\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 .= ''."\n"; - } - - $Str .= '
TitleTotal ProblemsAverage Correct
'.$title. - ''.$pCount. - ''.$crr. - '
'."\n"; - - return $Str; + return ($Str,$performance_length,$sum,$max); } #######################################################