--- loncom/interface/statistics/lonstudentassessment.pm 2002/08/01 20:49:06 1.6 +++ loncom/interface/statistics/lonstudentassessment.pm 2009/08/13 18:45:04 1.150.2.2 @@ -1,12 +1,10 @@ # The LearningOnline Network with CAPA -# (Publication Handler # -# $Id: lonstudentassessment.pm,v 1.6 2002/08/01 20:49:06 stredwic Exp $ +# $Id: lonstudentassessment.pm,v 1.150.2.2 2009/08/13 18:45:04 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). -# # 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 # the Free Software Foundation; either version 2 of the License, or @@ -26,602 +24,2159 @@ # http://www.lon-capa.org/ # # (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 Apache::lonhtmlcommon; +use Apache::lonstatistics(); +use Apache::lonhtmlcommon(); +use Apache::loncommon(); use Apache::loncoursedata; -use GDBM_File; +use Apache::lonnet; # for logging porpoises +use Apache::lonlocal; +use Apache::grades(); +use Apache::lonmsgdisplay(); +use Time::HiRes; +use Spreadsheet::WriteExcel; +use Spreadsheet::WriteExcel::Utility(); +use lib '/home/httpd/lib/perl/'; +use LONCAPA; + + +####################################################### +####################################################### +=pod + +=item Package Variables + +=over 4 + +=item $Statistics Hash ref to store student data. Indexed by symb, + contains hashes with keys 'score' and 'max'. + +=cut + +####################################################### +####################################################### + +my $Statistics; + +####################################################### +####################################################### + +=pod + +=item $show_links 'yes' or 'no' for linking to student performance data + +=item $output_mode 'html', 'excel', or 'csv' for output mode + +=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 + +####################################################### +####################################################### +my $show_links; +my $output_mode; +my $chosen_output; +my $single_student_mode; + +####################################################### +####################################################### +# End of package variable declarations + +=pod + +=back + +=cut + +####################################################### +####################################################### + +=pod + +=item &BuildStudentAssessmentPage() + +Inputs: + +=over 4 + +=item $r Apache Request + +=item $c Apache Connection + +=back -#my $jr; +=cut +####################################################### +####################################################### sub BuildStudentAssessmentPage { - my ($cacheDB,$students,$courseID,$formName,$headings,$spacing, - $studentInformation,$r,$c)=@_; -# $jr = $r; - my %cache; - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { - $r->print('
Unable to tie database.'); + my ($r,$c)=@_; + # + undef($Statistics); + undef($show_links); + undef($output_mode); + undef($chosen_output); + undef($single_student_mode); + # + my %Saveable_Parameters = ('Status' => 'scalar', + 'chartoutputmode' => 'scalar', + 'chartoutputdata' => 'scalar', + 'Section' => 'array', + 'Groups' => 'array', + 'StudentData' => 'array', + 'Maps' => 'array'); + &Apache::loncommon::store_course_settings('chart',\%Saveable_Parameters); + &Apache::loncommon::restore_course_settings('chart',\%Saveable_Parameters); + # + &Apache::lonstatistics::PrepareClasslist(); + # + $single_student_mode = 0; + $single_student_mode = 1 if ($env{'form.SelectedStudent'}); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + ['selectstudent']); + if ($env{'form.selectstudent'}) { + &Apache::lonstatistics::DisplayClasslist($r); return; } - - # Remove students who don't have the proper section. - my @sectionsSelected = split(':',$cache{'sectionsSelected'}); - for(my $studentIndex=((scalar @$students)-1); $studentIndex>=0; - $studentIndex--) { - my $value = $cache{$students->[$studentIndex].':section'}; - my $found = 0; - foreach (@sectionsSelected) { - if($_ eq 'none') { - if($value eq '' || !defined($value) || $value eq ' ') { - $found = 1; - last; - } - } else { - if($value eq $_) { - $found = 1; - last; - } - } - } - if($found == 0) { - splice(@$students, $studentIndex, 1); + # + # 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'}) && ! $single_student_mode) { + return; + } + $r->print(''."\n"); - foreach (@$students) { - if($c->aborted()) { return $Str; } - next if ($_ ne $selectedName && - $selectedName ne 'All Students'); - $selected = 1; - my $courseData; - my $downloadTime=''; - if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { - $downloadTime = $cache{$_.':lastDownloadTime'}; - untie(%cache); - } - if($downloadTime eq 'Not downloaded') { - $courseData = - &Apache::loncoursedata::DownloadCourseInformation($_, - $courseID); - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) { - next; - } - &Apache::loncoursedata::ProcessStudentData(\%cache, - $courseData, $_); - untie(%cache); - } - - next if($c->aborted()); - - 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) { - $Str .= '
'."\n"; - $Str .= ''. + &Apache::loncommon::help_open_topic("Chart_Sections"). + ' | '; + $Str .= ''.&mt('Groups').''. + ' | '; + $Str .= ''.&mt('Student Data'). + &Apache::loncommon::help_open_topic("Chart_Student_Data"). + ' | '; + $Str .= ''.&mt('Access Status').''. + &Apache::loncommon::help_open_topic("Chart_Enrollment_Status"). + ' | '; + $Str .= ''.&mt('Sequences and Folders').''. + &Apache::loncommon::help_open_topic("Chart_Sequences"). + ' | '; + $Str .= ''.&mt('Output Format').''. + &Apache::loncommon::help_open_topic("Chart_Output_Formats"). + ' | '; + $Str .= ''.&mt('Output Data').''. + &Apache::loncommon::help_open_topic("Chart_Output_Data"). + ' | '; + $Str .= '|||||||||||||||||||
'."\n"; - my @sections = split(':',$cache->{'sectionList'}); - my @selectedSections = split(':',$cache->{'sectionsSelected'}); - $Str .= &Apache::lonhtmlcommon::MultipleSectionSelect(\@sections, - \@selectedSections, - 'Statistics'); + $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); + $Str .= ' | '; + $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5); $Str .= ' | '; - $Str .= &CreateColumnSelectionBox($doNotShow); + $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple', + 5,undef); $Str .= ' | '."\n";
- $Str .= ''.
+ '';
+ $Str .= '';
+ $Str .= ' 'x5;
+ $Str .= '';
+ $Str .= ' '. + ''. + ' '; return $Str; } -sub CreateTableHeadings { - my($cache,$spacing,$infoKeys,$infoHeadings,$sequenceKeys, - $sequenceHeadings)=@_; +####################################################### +####################################################### - my $Str = ''; - $Str .= '
Output Formats\n"; + $Str .= "
Output Data\n"; + $Str .= "
'; - return $Str; - } - - 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}; - - # 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'})) { - $Str .= ' '; - $totalProblems++; - $characterCount++; - } - next; - } +=item &html_outputstudent($r,$student) - my %partData=undef; - # 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 - # 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 - next; - } +Return a line of the chart for a student. - my $tries=0; - my $code=' '; +=item &html_finish($r) - $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 = '*';} - 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; - } - } +=cut - # 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++; - } +####################################################### +####################################################### +{ + 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 + 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_cleanup { + undef(%prog_state); + undef(%width); + # + undef($navmap); + undef(@sequences); +} - $Str .= $partData{$_.':code'}; - $characterCount++; +sub html_initialize { + my ($r) = @_; + # + $padding = ' 'x3; + $count = 0; + $nodata_count = 0; + &html_cleanup(); + ($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.'). + ' '); + } - if($partData{$_.':code'} ne 'x') { - $totalProblems++; - } + # If we're showing links, show a checkbox to open in new + # windows. + if ($show_links ne 'no') { + my $labeltext = &mt('Show links in new window:'); + $r->print(<".$env{'course.'.$env{'request.course.id'}.'.description'}. + " ".&Apache::lonlocal::locallocaltime(time).""); + # + if ($chosen_output->{'base'} !~ /^final table/) { + $r->print("".&mt($chosen_output->{'shortdesc'}).""); + } + my $Str = "\n"; + # First, the @StudentData fields need to be listed + my @to_show = &get_student_fields_to_show(); + 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; + } + # + # Compute the column widths and output the sequence titles + my $total_count; + # + # 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 + $width{$symb}->{'width_sum'} += 1; + } + $total_count += &count_parts($navmap,$seq); + # Use 3 digits for the sum + $width{$symb}->{'width_sum'} += 3; + } + # Compute width of maximum + if ($chosen_output->{'sequence_max'}) { + if ($width{$symb}->{'width_sum'}>0) { + # One digit for the '/' + $width{$symb}->{'width_sum'} +=1; } - $Str.=' "; + $r->print($Str); + $r->rflush(); + + $r->print(< "); + } + # First, the @StudentData fields need to be listed + my @to_show = &get_student_fields_to_show(); + foreach my $field (@to_show) { + my $title=$student->{$field}; + # Deal with 'comments' - how I love special cases + if ($field eq 'comments') { + $title = ''.&mt('Comments').''; + } + my $base = length($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; + } else { + my $error = $1; + if (scalar(@tmp) < 1) { + $Str .= '' + .&mt('No Course Data') + .''."\n"; + } else { + $Str .= '' + .&mt('Error getting student data ([_1])',$error) + .''."\n"; + } + $nodata_count++; + $r->print($Str); + $r->rflush(); + return; + } + # + # By sequence build up the data + my $studentstats; + my $PerformanceStr = ''; + foreach my $seq (@sequences) { + my $symb = $seq->symb; + my ($performance,$performance_length,$score,$seq_max,$rawdata); + if ($chosen_output->{'tries'}) { + ($performance,$performance_length,$score,$seq_max,$rawdata) = + &student_tries_on_sequence($student,\%StudentsData, + $navmap,$seq,$show_links); + } else { + ($performance,$performance_length,$score,$seq_max,$rawdata) = + &student_performance_on_sequence($student,\%StudentsData, + $navmap,$seq,$show_links, + $chosen_output->{ignore_weight}); + } + my $ratio=''; + if ($chosen_output->{'every_problem'} && + $chosen_output->{'sequence_sum'}) { + $ratio .= ' '; + } + if ($chosen_output->{'sequence_sum'} && $score ne ' ') { + my $score .= sprintf("%3.0f",$score); + $ratio .= (' 'x(3-length($score))).$score; + } elsif($chosen_output->{'sequence_sum'}) { + $ratio .= ' 'x3; + } + if ($chosen_output->{'sequence_max'}) { + if ($chosen_output->{'sequence_sum'}) { + $ratio .= '/'; + } + $ratio .= sprintf("%3.0f",$seq_max); + } + # + if (! $chosen_output->{'every_problem'}) { + $performance = ''; + $performance_length=0; + } + $performance .= ' 'x($width{$symb}->{'width_total'} - + $performance_length - + $width{$symb}->{'width_sum'}). + $ratio; + # + $Str .= $performance.$padding; + # + $studentstats->{$symb}->{'score'}= $score; + $studentstats->{$symb}->{'max'} = $seq_max; + } + # + # Total it up and store the statistics info. + my ($score,$max); + while (my ($symb,$seq_stats) = each (%{$studentstats})) { + $Statistics->{$symb}->{'score'} += $seq_stats->{'score'}; + if ($Statistics->{$symb}->{'max'} < $seq_stats->{'max'}) { + $Statistics->{$symb}->{'max'} = $seq_stats->{'max'}; + } + if ($seq_stats->{'score'} ne ' ') { + $score += $seq_stats->{'score'}; + $Statistics->{$symb}->{'num_students'}++; + } + $max += $seq_stats->{'max'}; + } + if (! defined($score)) { + $score = ' ' x $total_sum_width; + } else { + $score = sprintf("%.0f",$score); + $score = (' 'x(3-length($score))).$score; + } + $Str .= ' '.' 'x($total_sum_width-length($score)).$score.' / '.$max; + $Str .= " \n"; + # + $r->print($Str); + # + $r->rflush(); + &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); + return; +} -sub CreateLegend { - my $Str = "\n"); + if ($chosen_output->{'summary_table'}) { + if ($single_student_mode) { + $r->print(&SingleStudentTotal()); + } else { + $r->print(&StudentAverageTotal()); + } + } + $r->rflush(); + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + &html_cleanup(); + return; } -=pod - -=item &CreateColumnSelectionBox() +sub StudentAverageTotal { + 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". - " '.&mt('Summary Tables').''.$/; + $Str .= &Apache::loncommon::start_data_table(); + $Str .= &Apache::loncommon::start_data_table_header_row(). + ' | '.&mt('Title').' | '. + ''.&mt('Average').' | '. + ''.&mt('Maximum').' | '. + &Apache::loncommon::end_data_table_header_row().$/; + foreach my $seq (@sequences) { + my $symb = $seq->symb; + my $ave; + my $num_students = $Statistics->{$symb}->{'num_students'}; + if ($num_students > 0) { + $ave = int(100* + ($Statistics->{$symb}->{'score'}/$num_students) + )/100; + } else { + $ave = 0; + } + my $max = $Statistics->{$symb}->{'max'}; + $ave = sprintf("%.2f",$ave); + $Str .= &Apache::loncommon::start_data_table_row(). + ''.$seq->compTitle.' | '. + ''.$ave.' | '. + ''.$max.' '.' | '. + &Apache::loncommon::end_data_table_row()."\n"; + } + $Str .= &Apache::loncommon::end_data_table()."\n"; + return $Str; +} -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. +sub SingleStudentTotal { + return if (! defined($navmap)); + my $student = &Apache::lonstatistics::current_student(); + my $Str = ''.&mt('Sequence or Folder').' | '; + if ($chosen_output->{'base'} eq 'tries') { + $Str .= ''.&mt('Parts Correct').' | '; + } else { + $Str .= ''.&mt('Score').' | '; + } + $Str .= ''.&mt('Maximum').' | '. + &Apache::loncommon::end_data_table_header_row()."\n"; + my $total = 0; + my $total_max = 0; + foreach my $seq (@sequences) { + my $value = $Statistics->{$seq->symb}->{'score'}; + my $max = $Statistics->{$seq->symb}->{'max'}; + $Str .= &Apache::loncommon::start_data_table_row(). + ''.&HTML::Entities::encode($seq->compTitle).' | '. + ''.$value.' | '. + ''.$max.' | '. + &Apache::loncommon::end_data_table_row()."\n"; + $total += $value; + $total_max +=$max; + } + $Str .= &Apache::loncommon::start_data_table_row(). + ''.&mt('Total').' | '. + ''.$total.' | '. + ''.$total_max.' | '. + &Apache::loncommon::end_data_table_row()."\n"; + $Str .= &Apache::loncommon::end_data_table()."\n"; + return $Str; +} -=over 4 -Input: $CacheData, $headings +} +####################################################### +####################################################### -$CacheData: A pointer to a hash tied to the cached data +=pod -$headings: An array of the names of the columns for the student information. -They are used for displaying which columns are missing. +=head2 EXCEL subroutines -Output: $notThere +=item &excel_initialize($r) -$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. +=item &excel_outputstudent($r,$student) -=back +=item &excel_finish($r) =cut -sub CreateColumnSelectionBox { - my ($doNotShow)=@_; +####################################################### +####################################################### +{ + +my $excel_sheet; +my $excel_workbook; +my $format; + +my $filename; +my $rows_output; +my $cols_output; + +my %prog_state; # progress window state +my $request_aborted; + +my $total_formula; +my $maximum_formula; +my %formula_data; + +my $navmap; +my @sequences; + +sub excel_cleanup { + # + undef ($excel_sheet); + undef ($excel_workbook); + undef ($filename); + undef ($rows_output); + undef ($cols_output); + undef (%prog_state); + undef ($request_aborted); + undef ($total_formula); + undef ($maximum_formula); + # + undef(%formula_data); + # + undef($navmap); + undef(@sequences); +} - my $notThere = ''; - $notThere .= ''; + my $start = $formula_data{$symb}->{'Excel:startcell'}; + my $end = $formula_data{$symb}->{'Excel:endcell'}; + $formula_data{$symb}->{'Excel:sum'}= $excel_sheet->store_formula + ("=IF(COUNT($start\:$end),SUM($start\:$end),\"\")"); + # Determine cell the score is held in + $formula_data{$symb}->{'Excel:scorecell'} = + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row,$cols_output); + $formula_data{$symb}->{'Excel:scorecol'}=$cols_output; + if ($chosen_output->{'base'} eq 'parts correct total') { + $excel_sheet->write($resource_name_row,$cols_output++, + 'parts correct', + $format->{'bold'}); + } elsif ($chosen_output->{'sequence_sum'}) { + if ($chosen_output->{'correct'}) { + # Only reporting the number correct, so do not call it score + $excel_sheet->write($resource_name_row,$cols_output++, + 'sum', + $format->{'bold'}); + } else { + $excel_sheet->write($resource_name_row,$cols_output++, + 'score', + $format->{'bold'}); + } + } + # + $total_formula_string.='+'. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row,$cols_output-1); + if ($chosen_output->{'sequence_max'}) { + $excel_sheet->write($resource_name_row,$cols_output, + 'maximum', + $format->{'bold'}); + $formula_data{$symb}->{'Excel:maxcell'} = + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row,$cols_output); + $formula_data{$symb}->{'Excel:maxcol'}=$cols_output; + $maximum_formula_string.='+'. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row,$cols_output); + $cols_output++; - return $notThere; + } + } + if ($chosen_output->{'grand_total'}) { + $excel_sheet->write($resource_name_row,$cols_output++,'Total', + $format->{'bold'}); + } + if ($chosen_output->{'grand_maximum'}) { + $excel_sheet->write($resource_name_row,$cols_output++,'Max. Total', + $format->{'bold'}); + } + $total_formula = $excel_sheet->store_formula($total_formula_string); + $maximum_formula = $excel_sheet->store_formula($maximum_formula_string); + ############################################## + # Output a row for MAX, if appropriate + ############################################## + if ($chosen_output->{'maximum_row'}) { + $cols_output = 0; + foreach my $field (&get_student_fields_to_show()) { + if ($field eq 'username' || $field eq 'fullname' || + $field eq 'id') { + $excel_sheet->write($maximum_data_row,$cols_output++,'Maximum', + $format->{'bold'}); + } else { + $excel_sheet->write($maximum_data_row,$cols_output++,''); + } + } + # + # Add the maximums for each sequence or assessment + my %total_cell_translation; + my %maximum_cell_translation; + foreach my $seq (@sequences) { + my $symb = $seq->symb; + $cols_output=$formula_data{$symb}->{'Excel:startcol'}; + $total_cell_translation{$formula_data{$symb}->{'Excel:scorecell'}}= + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($maximum_data_row,$formula_data{$symb}->{'Excel:scorecol'}); + $maximum_cell_translation{$formula_data{$symb}->{'Excel:maxcell'}}= + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($maximum_data_row,$formula_data{$symb}->{'Excel:maxcol'}); + my $weight; + my $max = 0; + foreach my $resource (&get_resources($navmap,$seq)) { + foreach my $part (@{$resource->parts}){ + $weight = 1; + if ($chosen_output->{'scores'}) { + $weight = &Apache::lonnet::EXT + ('resource.'.$part.'.weight',$resource->symb, + undef,undef,undef); + if (!defined($weight) || ($weight eq '')) { + $weight=1; + } + } + if ($chosen_output->{'scores'} && + $chosen_output->{'every_problem'}) { + $excel_sheet->write($maximum_data_row,$cols_output++, + $weight); + } + $max += $weight; + } + } + # + if ($chosen_output->{'sequence_sum'} && + $chosen_output->{'every_problem'}) { + my %replaceCells= + ('^'.$formula_data{$symb}->{'Excel:startcell'}.':'. + $formula_data{$symb}->{'Excel:endcell'}.'$' => + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$formula_data{$symb}->{'Excel:startcol'}).':'. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$formula_data{$symb}->{'Excel:endcol'})); + $excel_sheet->repeat_formula($maximum_data_row,$cols_output++, + $formula_data{$symb}->{'Excel:sum'},undef, + %replaceCells, %replaceCells); + + } elsif ($chosen_output->{'sequence_sum'}) { + $excel_sheet->write($maximum_data_row,$cols_output++,$max); + } + if ($chosen_output->{'sequence_max'}) { + $excel_sheet->write($maximum_data_row,$cols_output++,$max); + } + # + } + if ($chosen_output->{'grand_total'}) { + $excel_sheet->repeat_formula($maximum_data_row,$cols_output++, + $total_formula,undef, + %total_cell_translation); + } + if ($chosen_output->{'grand_maximum'}) { + $excel_sheet->repeat_formula($maximum_data_row,$cols_output++, + $maximum_formula,undef, + %maximum_cell_translation); + } + } # End of MAXIMUM row output if ($chosen_output->{'maximum_row'}) { + $rows_output = $first_data_row; + ############################################## + # Output summary table, which actually is above the sequence name row. + ############################################## + if ($chosen_output->{'summary_table'}) { + $cols_output = 0; + $excel_sheet->write($summary_header_row,$cols_output++, + 'Summary Table',$format->{'bold'}); + if ($chosen_output->{'maximum_row'}) { + $excel_sheet->write($summary_header_row,$cols_output++, + 'Maximum',$format->{'bold'}); + } + $excel_sheet->write($summary_header_row,$cols_output++, + 'Average',$format->{'bold'}); + $excel_sheet->write($summary_header_row,$cols_output++, + 'Median',$format->{'bold'}); + $excel_sheet->write($summary_header_row,$cols_output++, + 'Std Dev',$format->{'bold'}); + my $row = $summary_header_row+1; + foreach my $seq (@sequences) { + my $symb = $seq->symb; + $cols_output = 0; + $excel_sheet->write($row,$cols_output++, + $seq->compTitle, + $format->{'bold'}); + if ($chosen_output->{'maximum_row'}) { + $excel_sheet->write + ($row,$cols_output++, + '='. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($maximum_data_row,$formula_data{$symb}->{'Excel:scorecol'}) + ); + } + my $range = + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row,$formula_data{$symb}->{'Excel:scorecol'}). + ':'. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($first_data_row+$num_students-1,$formula_data{$symb}->{'Excel:scorecol'}); + $excel_sheet->write($row,$cols_output++, + '=AVERAGE('.$range.')'); + $excel_sheet->write($row,$cols_output++, + '=MEDIAN('.$range.')'); + $excel_sheet->write($row,$cols_output++, + '=STDEV('.$range.')'); + $row++; + } + } + ############################################## + # Take care of non-excel initialization + ############################################## + # + # Let the user know what we are doing + my $studentcount = scalar(@Apache::lonstatistics::Students); + if ($env{'form.SelectedStudent'}) { + $studentcount = '1'; + } + $r->print(''; - $present .= '[$index].'" />'; - $present .= ' | '."\n"; +sub csv_outputstudent { + my ($r,$student) = @_; + if ($request_aborted || ! defined($navmap) || ! defined($outputfile)) { + return; + } + my $Str = ''; + # + # Output student fields + my @to_show = &get_student_fields_to_show(); + foreach my $field (@to_show) { + my $value = $student->{$field}; + if ($field eq 'comments') { + $value = &Apache::lonmsgdisplay::retrieve_instructor_comments + ($student->{'username'},$student->{'domain'}); + } + $Str .= '"'.&Apache::loncommon::csv_translate($value).'",'; + } + # + # 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 + my $total = 0; + my $maximum = 0; + foreach my $seq (@sequences) { + my ($performance,$performance_length,$score,$seq_max,$rawdata); + if ($chosen_output->{'tries'}){ + ($performance,$performance_length,$score,$seq_max,$rawdata) = + &student_tries_on_sequence($student,\%StudentsData, + $navmap,$seq,'no'); + } else { + ($performance,$performance_length,$score,$seq_max,$rawdata) = + &student_performance_on_sequence($student,\%StudentsData, + $navmap,$seq,'no', + $chosen_output->{ignore_weight}); + } + if ($chosen_output->{'every_problem'}) { + if ($chosen_output->{'correct'}) { + $score = 0; + # Deal with number of parts correct data + $Str .= '"'.join('","',( map { if ($_>0) { + $score += 1; + 1; + } else { + 0; + } + } @$rawdata)).'",'; + } else { + $Str .= '"'.join('","',(@$rawdata)).'",'; + } + } + if ($chosen_output->{'sequence_sum'}) { + $Str .= '"'.$score.'",'; + } + if ($chosen_output->{'sequence_max'}) { + $Str .= '"'.$seq_max.'",'; + } + $total+=$score; + $maximum += $seq_max; } + if ($chosen_output->{'grand_total'}) { + $Str .= '"'.$total.'",'; + } + if ($chosen_output->{'grand_maximum'}) { + $Str .= '"'.$maximum.'",'; + } + chop($Str); + $Str .= "\n"; + print $outputfile $Str; + # + # Update the progress window + &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); + return; +} - for(my $index=0; $index<(scalar @$sequenceHeadings); $index++) { - $present .= ''; - $present .= '[$index].'" />'; - $present .= ' | '."\n"; +sub csv_finish { + my ($r) = @_; + if ($request_aborted || ! defined($navmap) || ! defined($outputfile)) { + &csv_cleanup(); + return; } + close($outputfile); + # + # Close the progress window + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + # + # Tell the user where to get their csv file + $r->print('
---|