Diff for /loncom/interface/statistics/lonproblemanalysis.pm between versions 1.70 and 1.71

version 1.70, 2004/02/18 19:16:55 version 1.71, 2004/02/19 20:17:01
Line 33  use Apache::lonhtmlcommon(); Line 33  use Apache::lonhtmlcommon();
 use Apache::loncoursedata();  use Apache::loncoursedata();
 use Apache::lonstatistics;  use Apache::lonstatistics;
 use Apache::lonlocal;  use Apache::lonlocal;
 use Apache::lonstathelpers;  use Apache::lonstathelpers();
   use Apache::lonstudentsubmissions();
 use HTML::Entities();  use HTML::Entities();
 use Time::Local();  use Time::Local();
 use Spreadsheet::WriteExcel();  use Spreadsheet::WriteExcel();
Line 146  sub BuildProblemAnalysisPage { Line 147  sub BuildProblemAnalysisPage {
             $r->print('<h3>'.$resource->{'src'}.'</h3>');              $r->print('<h3>'.$resource->{'src'}.'</h3>');
             $r->print(&Apache::lonstathelpers::render_resource($resource));              $r->print(&Apache::lonstathelpers::render_resource($resource));
             $r->rflush();              $r->rflush();
             my %Data = &get_problem_data($resource->{'src'});              my %Data = &Apache::lonstathelpers::get_problem_data
                   ($resource->{'src'});
             my $ProblemData = $Data{$current_problem->{'part'}.              my $ProblemData = $Data{$current_problem->{'part'}.
                                     '.'.                                      '.'.
                                     $current_problem->{'respid'}};                                      $current_problem->{'respid'}};
Line 160  sub BuildProblemAnalysisPage { Line 162  sub BuildProblemAnalysisPage {
                                        \@Students);                                         \@Students);
             } elsif ($current_problem->{'resptype'} eq 'numerical') {              } elsif ($current_problem->{'resptype'} eq 'numerical') {
 #                if (exists($ENV{'form.ExcelOutput'})) {  #                if (exists($ENV{'form.ExcelOutput'})) {
                     &prepare_excel_output($r,$current_problem,                      &Apache::lonstudentsubmissions::prepare_excel_output
                                           $ProblemData,\@Students);                          ($r,$current_problem,$ProblemData,\@Students);
 #                } else {  #                } else {
 #                    &NumericalResponseAnalysis($r,$current_problem,  #                    &NumericalResponseAnalysis($r,$current_problem,
 #                                               $ProblemData,\@Students);  #                                               $ProblemData,\@Students);
Line 185  sub BuildProblemAnalysisPage { Line 187  sub BuildProblemAnalysisPage {
 #########################################################  #########################################################
 #########################################################  #########################################################
 ##  ##
 ##      Excel output of student answers and correct answers  
 ##  
 #########################################################  
 #########################################################  
 sub prepare_excel_output {  
     my ($r,$problem,$ProblemData,$Students) = @_;  
     my ($resource,$respid,$partid) = ($problem->{'resource'},  
                                       $problem->{'respid'},  
                                       $problem->{'part'});  
     $r->print('<h2>'.  
               &mt('Preparing Excel spreadsheet of student responses').  
               '</h2>');  
     #  
     &GetStudentAnswers($r,$problem,$Students);  
     #  
     my @Columns = ( 'username','domain','attempt','time',  
                     'submission','correct', 'grading','awarded','weight',  
                     'score');  
     my $awarded_col = 7;  
     my $weight_col  = 8;  
     #  
     # Create excel worksheet  
     my $filename = '/prtspool/'.  
         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.  
         time.'_'.rand(1000000000).'.xls';  
     my $workbook  = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);  
     if (! defined($workbook)) {  
         $r->log_error("Error creating excel spreadsheet $filename: $!");  
         $r->print('<p>'.&mt("Unable to create new Excel file.  ".  
                             "This error has been logged.  ".  
                             "Please alert your LON-CAPA administrator").  
                   '</p>');  
         return undef;  
     }  
     #  
     $workbook->set_tempdir('/home/httpd/perl/tmp');  
     #  
     my $format = &Apache::loncommon::define_excel_formats($workbook);  
     my $worksheet  = $workbook->addworksheet('Student Submission Data');  
     #  
     # Make sure we get new weight data instead of data on a 10 minute delay  
     &Apache::lonnet::clear_EXT_cache_status();  
     #  
     # Put on the standard headers and whatnot  
     my $rows_output=0;  
     $worksheet->write($rows_output++,0,$resource->{'title'},$format->{'h1'});  
     $worksheet->write($rows_output++,0,$resource->{'src'},$format->{'h3'});  
     $rows_output++;  
     $worksheet->write_row($rows_output++,0,\@Columns,$format->{'bold'});  
     #  
     # Populate the worksheet with the student data  
     foreach my $student (@$Students) {  
         my $results = &Apache::loncoursedata::get_response_data_by_student  
             ($student,$resource->{'symb'},$respid);  
         my %row;  
         $row{'username'} = $student->{'username'};  
         $row{'domain'}   = $student->{'domain'};  
         $row{'correct'} = $student->{'answer'};  
         $row{'weight'} = &Apache::lonnet::EXT  
             ('resource.'.$partid.'.weight',$resource->{'symb'},  
              undef,undef,undef);  
         if (! defined($results) || ref($results) ne 'ARRAY') {  
             $row{'score'} = '='.  
                 &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell  
                     ($rows_output,$awarded_col)  
                 .'*'.  
                 &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell  
                     ($rows_output,$weight_col);  
             my $cols_output = 0;  
             foreach my $col (@Columns) {  
                 if (! exists($row{$col})) {  
                     $cols_output++;  
                     next;  
                 }  
                 $worksheet->write($rows_output,$cols_output++,$row{$col});  
             }  
             $rows_output++;  
         } else {  
             foreach my $response (@$results) {  
                 delete($row{'time'});  
                 delete($row{'attempt'});  
                 delete($row{'submission'});  
                 delete($row{'awarded'});  
                 delete($row{'grading'});  
                 delete($row{'score'});  
                 my %row_format;  
                 #  
                 # Time is handled differently  
                 $row{'time'} = &calc_serial(  
                      $response->[&Apache::loncoursedata::RDs_timestamp()]);  
                 $row_format{'time'}=$format->{'date'};  
                 #  
                 $row{'attempt'}  = $response->[  
                      &Apache::loncoursedata::RDs_tries()];  
                 $row{'submission'} = $response->[  
                      &Apache::loncoursedata::RDs_submission()];  
                 $row{'grading'} = $response->[  
                      &Apache::loncoursedata::RDs_awarddetail()];  
                 $row{'awarded'} = $response->[  
                      &Apache::loncoursedata::RDs_awarded()];  
                 $row{'score'} = '='.  
                     &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell  
                         ($rows_output,$awarded_col)  
                     .'*'.  
                     &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell  
                         ($rows_output,$weight_col);  
                 my $cols_output = 0;  
                 foreach my $col (@Columns) {  
                     $worksheet->write($rows_output,$cols_output++,$row{$col},  
                                       $row_format{$col});  
                 }  
                 $rows_output++;  
             }  
         } # End of else clause on if (! defined($results) ....  
     }  
     #  
     # Close the excel file  
     $workbook->close();  
     #  
     # Write a link to allow them to download it  
     $r->print('<p><a href="'.$filename.'">'.  
               &mt('Your Excel spreadsheet.').  
               '</a></p>'."\n");  
   
 }  
   
   
 #########################################################  
 #########################################################  
 ##  
 ##      Numerical Response Routines  ##      Numerical Response Routines
 ##  ##
 #########################################################  #########################################################
Line 385  sub GetStudentAnswers { Line 257  sub GetStudentAnswers {
     foreach my $student (@$Students) {      foreach my $student (@$Students) {
         my $sname = $student->{'username'};          my $sname = $student->{'username'};
         my $sdom = $student->{'domain'};          my $sdom = $student->{'domain'};
         my $answer = &analyze_problem_as_student($resource,          my $answer = &Apache::lonstathelpers::analyze_problem_as_student
                                                  $sname,$sdom,              ($resource,$sname,$sdom,$partid,$respid);
                                                  $partid,$respid);  
         &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,          &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
                                                  &mt('last student'));                                                   &mt('last student'));
         $student->{'answer'} = $answer;          $student->{'answer'} = $answer;
Line 1258  sub OR_build_response_data_worksheet { Line 1129  sub OR_build_response_data_worksheet {
         }          }
         $worksheet->write($rows_output,$cols_output++,$student);          $worksheet->write($rows_output,$cols_output++,$student);
         $worksheet->write($rows_output,$cols_output++,          $worksheet->write($rows_output,$cols_output++,
                           &calc_serial($time),$format->{'date'});               &Apache::lonstathelpers::calc_serial($time),$format->{'date'});
         $worksheet->write($rows_output,$cols_output++,$award);          $worksheet->write($rows_output,$cols_output++,$award);
         $worksheet->write($rows_output,$cols_output++,$tries);          $worksheet->write($rows_output,$cols_output++,$tries);
         foreach my $foilid (@$Foils) {          foreach my $foilid (@$Foils) {
Line 1273  sub OR_build_response_data_worksheet { Line 1144  sub OR_build_response_data_worksheet {
     return;      return;
 }  }
   
   
 ##  
 ## The following is copied from datecalc1.pl, part of the   
 ## Spreadsheet::WriteExcel CPAN module.  
 ##  
 ##  
 ######################################################################  
 #  
 # Demonstration of writing date/time cells to Excel spreadsheets,  
 # using UNIX/Perl time as source of date/time.  
 #  
 # Copyright 2000, Andrew Benham, adsb@bigfoot.com  
 #  
 ######################################################################  
 #  
 # UNIX/Perl time is the time since the Epoch (00:00:00 GMT, 1 Jan 1970)  
 # measured in seconds.  
 #  
 # An Excel file can use exactly one of two different date/time systems.  
 # In these systems, a floating point number represents the number of days  
 # (and fractional parts of the day) since a start point. The floating point  
 # number is referred to as a 'serial'.  
 # The two systems ('1900' and '1904') use different starting points:  
 #  '1900'; '1.00' is 1 Jan 1900 BUT 1900 is erroneously regarded as  
 #          a leap year - see:  
 #            http://support.microsoft.com/support/kb/articles/Q181/3/70.asp  
 #          for the excuse^H^H^H^H^H^Hreason.  
 #  '1904'; '1.00' is 2 Jan 1904.  
 #  
 # The '1904' system is the default for Apple Macs. Windows versions of  
 # Excel have the option to use the '1904' system.  
 #  
 # Note that Visual Basic's "DateSerial" function does NOT erroneously  
 # regard 1900 as a leap year, and thus its serials do not agree with  
 # the 1900 serials of Excel for dates before 1 Mar 1900.  
 #  
 # Note that StarOffice (at least at version 5.2) does NOT erroneously  
 # regard 1900 as a leap year, and thus its serials do not agree with  
 # the 1900 serials of Excel for dates before 1 Mar 1900.  
 #  
 ######################################################################  
 #  
 # Calculation description  
 # =======================  
 #  
 # 1900 system  
 # -----------  
 # Unix time is '0' at 00:00:00 GMT 1 Jan 1970, i.e. 70 years after 1 Jan 1900.  
 # Of those 70 years, 17 (1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68)  
 # were leap years with an extra day.  
 # Thus there were 17 + 70*365 days = 25567 days between 1 Jan 1900 and  
 # 1 Jan 1970.  
 # In the 1900 system, '1' is 1 Jan 1900, but as 1900 was not a leap year  
 # 1 Jan 1900 should really be '2', so 1 Jan 1970 is '25569'.  
 #  
 # 1904 system  
 # -----------  
 # Unix time is '0' at 00:00:00 GMT 1 Jan 1970, i.e. 66 years after 1 Jan 1904.  
 # Of those 66 years, 17 (1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68)  
 # were leap years with an extra day.  
 # Thus there were 17 + 66*365 days = 24107 days between 1 Jan 1904 and  
 # 1 Jan 1970.  
 # In the 1904 system, 2 Jan 1904 being '1', 1 Jan 1970 is '24107'.  
 #  
 ######################################################################  
 #  
 # Copyright (c) 2000, Andrew Benham.  
 # This program is free software. It may be used, redistributed and/or  
 # modified under the same terms as Perl itself.  
 #  
 # Andrew Benham, adsb@bigfoot.com  
 # London, United Kingdom  
 # 11 Nov 2000  
 #  
 ######################################################################  
   
 # Use 1900 date system on all platforms other than Apple Mac (for which  
 # use 1904 date system).  
 my $DATE_SYSTEM = ($^O eq 'MacOS') ? 1 : 0;  
   
 #-----------------------------------------------------------  
 # calc_serial()  
 #  
 # Called with (up to) 2 parameters.  
 #   1.  Unix timestamp.  If omitted, uses current time.  
 #   2.  GMT flag. Set to '1' to return serial in GMT.  
 #       If omitted, returns serial in appropriate timezone.  
 #  
 # Returns date/time serial according to $DATE_SYSTEM selected  
 #-----------------------------------------------------------  
 sub calc_serial {  
         my $time = (defined $_[0]) ? $_[0] : time();  
         my $gmtflag = (defined $_[1]) ? $_[1] : 0;  
   
         # Divide timestamp by number of seconds in a day.  
         # This gives a date serial with '0' on 1 Jan 1970.  
         my $serial = $time / 86400;  
   
         # Adjust the date serial by the offset appropriate to the  
         # currently selected system (1900/1904).  
         if ($DATE_SYSTEM == 0) {        # use 1900 system  
                 $serial += 25569;  
         } else {                        # use 1904 system  
                 $serial += 24107;  
         }  
   
         unless ($gmtflag) {  
                 # Now have a 'raw' serial with the right offset. But this  
                 # gives a serial in GMT, which is false unless the timezone  
                 # is GMT. We need to adjust the serial by the appropriate  
                 # timezone offset.  
                 # Calculate the appropriate timezone offset by seeing what  
                 # the differences between localtime and gmtime for the given  
                 # time are.  
   
                 my @gmtime = gmtime($time);  
                 my @ltime  = localtime($time);  
   
                 # For the first 7 elements of the two arrays, adjust the  
                 # date serial where the elements differ.  
                 for (0 .. 6) {  
                         my $diff = $ltime[$_] - $gmtime[$_];  
                         if ($diff) {  
                                 $serial += _adjustment($diff,$_);  
                         }  
                 }  
         }  
   
         # Perpetuate the error that 1900 was a leap year by decrementing  
         # the serial if we're using the 1900 system and the date is prior to  
         # 1 Mar 1900. This has the effect of making serial value '60'  
         # 29 Feb 1900.  
   
         # This fix only has any effect if UNIX/Perl time on the platform  
         # can represent 1900. Many can't.  
   
         unless ($DATE_SYSTEM) {  
                 $serial-- if ($serial < 61);    # '61' is 1 Mar 1900  
         }  
         return $serial;  
 }  
   
 sub _adjustment {  
         # Based on the difference in the localtime/gmtime array elements  
         # number, return the adjustment required to the serial.  
   
         # We only look at some elements of the localtime/gmtime arrays:  
         #    seconds    unlikely to be different as all known timezones  
         #               have an offset of integral multiples of 15 minutes,  
         #               but it's easy to do.  
         #    minutes    will be different for timezone offsets which are  
         #               not an exact number of hours.  
         #    hours      very likely to be different.  
         #    weekday    will differ when localtime/gmtime difference  
         #               straddles midnight.  
         #  
         # Assume that difference between localtime and gmtime is less than  
         # 5 days, then don't have to do maths for day of month, month number,  
         # year number, etc...  
   
         my ($delta,$element) = @_;  
         my $adjust = 0;  
   
         if ($element == 0) {            # Seconds  
                 $adjust = $delta/86400;         # 60 * 60 * 24  
         } elsif ($element == 1) {       # Minutes  
                 $adjust = $delta/1440;          # 60 * 24  
         } elsif ($element == 2) {       # Hours  
                 $adjust = $delta/24;            # 24  
         } elsif ($element == 6) {       # Day of week number  
                 # Catch difference straddling Sat/Sun in either direction  
                 $delta += 7 if ($delta < -4);  
                 $delta -= 7 if ($delta > 4);  
   
                 $adjust = $delta;  
         }  
         return $adjust;  
 }  
   
 sub build_foil_index {  sub build_foil_index {
     my ($ORdata) = @_;      my ($ORdata) = @_;
     return if (! exists($ORdata->{'_Foils'}));      return if (! exists($ORdata->{'_Foils'}));
Line 1764  sub Process_OR_Row { Line 1456  sub Process_OR_Row {
     return %RowData;      return %RowData;
 }  }
   
   
 sub analyze_problem_as_student {  
     my ($resource,$sname,$sdom,$partid,$respid) = @_;  
     my $url = $resource->{'src'};  
     my $symb = $resource->{'symb'};  
     my $courseid = $ENV{'request.course.id'};  
     my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze',  
                                         'grade_domain' => $sdom,  
                                         'grade_username' => $sname,  
                                         'grade_symb' => $symb,  
                                         'grade_courseid' => $courseid));  
     (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);  
     my %Answer=&Apache::lonnet::str2hash($Answ);  
     my $key = $partid.'.'.$respid.'.answer';  
     my $student_answer = $Answer{$key}->[0];  
     if (! defined($student_answer)) {  
         $student_answer = $Answer{$key}->[1];  
     }  
     return ($student_answer);  
 }  
   
 ##  
 ## get problem data and put it into a useful data structure.  
 ## note: we must force each foil and option to not begin or end with  
 ##       spaces as they are stored without such data.  
 ##  
 sub get_problem_data {  
     my ($url) = @_;  
     my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));  
     (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);  
     my %Answer;  
     %Answer=&Apache::lonnet::str2hash($Answ);  
     my %Partdata;  
     foreach my $part (@{$Answer{'parts'}}) {  
         while (my($key,$value) = each(%Answer)) {  
             #  
             # Logging code:  
             if (0) {  
                 &Apache::lonnet::logthis($part.' got key "'.$key.'"');  
                 if (ref($value) eq 'ARRAY') {  
                     &Apache::lonnet::logthis('    '.join(',',@$value));  
                 } else {  
                     &Apache::lonnet::logthis('    '.$value);  
                 }  
             }  
             # End of logging code  
             next if ($key !~ /^$part/);  
             $key =~ s/^$part\.//;  
             if (ref($value) eq 'ARRAY') {  
                 if ($key eq 'options') {  
                     $Partdata{$part}->{'_Options'}=$value;  
                 } elsif ($key eq 'concepts') {  
                     $Partdata{$part}->{'_Concepts'}=$value;  
                 } elsif ($key =~ /^concept\.(.*)$/) {  
                     my $concept = $1;  
                     foreach my $foil (@$value) {  
                         $Partdata{$part}->{'_Foils'}->{$foil}->{'_Concept'}=  
                                                                       $concept;  
                     }  
                 } elsif ($key =~ /^(incorrect|answer|ans_low|ans_high)$/) {  
                     $Partdata{$part}->{$key}=$value;  
                 }  
             } else {  
                 if ($key=~ /^foil\.text\.(.*)$/) {  
                     my $foil = $1;  
                     $Partdata{$part}->{'_Foils'}->{$foil}->{'name'}=$foil;  
                     $value =~ s/(\s*$|^\s*)//g;  
                     $Partdata{$part}->{'_Foils'}->{$foil}->{'text'}=$value;  
                 } elsif ($key =~ /^foil\.value\.(.*)$/) {  
                     my $foil = $1;  
                     $Partdata{$part}->{'_Foils'}->{$foil}->{'value'}=$value;  
                 }  
             }  
         }  
     }  
     return %Partdata;  
 }  
   
 1;  1;
   
 __END__  __END__

Removed from v.1.70  
changed lines
  Added in v.1.71


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>