[LON-CAPA-cvs] cvs: loncom /interface lonstatistics.pm /interface/statistics lonproblemanalysis.pm lonstathelpers.pm lonstudentsubmissions.pm doc/loncapafiles loncapafiles.lpml
matthew
lon-capa-cvs@mail.lon-capa.org
Thu, 19 Feb 2004 20:17:01 -0000
This is a MIME encoded message
--matthew1077221821
Content-Type: text/plain
matthew Thu Feb 19 15:17:01 2004 EDT
Added files:
/loncom/interface/statistics lonstudentsubmissions.pm
Modified files:
/doc/loncapafiles loncapafiles.lpml
/loncom/interface lonstatistics.pm
/loncom/interface/statistics lonproblemanalysis.pm
lonstathelpers.pm
Log:
Added lonstudentsubmissions.pm
Moved code from lonproblemanalysis to lonstathelpers and lonstudentsubmissions.
lonstudentsubmissions can produce excel spreadsheets of student submissions
for numerical, radio, option, click on image, string, and formula responses.
--matthew1077221821
Content-Type: text/plain
Content-Disposition: attachment; filename="matthew-20040219151701.txt"
Index: doc/loncapafiles/loncapafiles.lpml
diff -u doc/loncapafiles/loncapafiles.lpml:1.337 doc/loncapafiles/loncapafiles.lpml:1.338
--- doc/loncapafiles/loncapafiles.lpml:1.337 Wed Feb 18 03:07:15 2004
+++ doc/loncapafiles/loncapafiles.lpml Thu Feb 19 15:17:01 2004
@@ -2,7 +2,7 @@
"http://lpml.sourceforge.net/DTD/lpml.dtd">
<!-- loncapafiles.lpml -->
-<!-- $Id: loncapafiles.lpml,v 1.337 2004/02/18 08:07:15 www Exp $ -->
+<!-- $Id: loncapafiles.lpml,v 1.338 2004/02/19 20:17:01 matthew Exp $ -->
<!--
@@ -2441,6 +2441,15 @@
<categoryname>handler</categoryname>
<description>
The module that generate the html for the Problem Statistics web pages.
+</description>
+</file>
+<file>
+<source>loncom/interface/statistics/lonstudentsubmissions.pm</source>
+<target dist='default'>home/httpd/lib/perl/Apache/lonstudentsubmissions.pm
+</target>
+<categoryname>handler</categoryname>
+<description>
+Generates excel file containing student submissions and correct answers.
</description>
</file>
<file>
Index: loncom/interface/lonstatistics.pm
diff -u loncom/interface/lonstatistics.pm:1.96 loncom/interface/lonstatistics.pm:1.97
--- loncom/interface/lonstatistics.pm:1.96 Thu Feb 12 17:23:30 2004
+++ loncom/interface/lonstatistics.pm Thu Feb 19 15:17:01 2004
@@ -1,6 +1,6 @@
# The LearningOnline Network with CAPA
#
-# $Id: lonstatistics.pm,v 1.96 2004/02/12 22:23:30 matthew Exp $
+# $Id: lonstatistics.pm,v 1.97 2004/02/19 20:17:01 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -65,15 +65,18 @@
use Apache::loncommon;
use Apache::loncoursedata;
use Apache::lonhtmlcommon;
+use Apache::lonmysql;
+use Apache::lonlocal;
+use Time::HiRes;
+#
+# Statistics Packages
use Apache::lonproblemanalysis();
use Apache::lonsubmissiontimeanalysis();
use Apache::loncorrectproblemplot();
use Apache::lonproblemstatistics();
use Apache::lonstudentassessment();
use Apache::lonpercentage;
-use Apache::lonmysql;
-use Apache::lonlocal;
-use Time::HiRes;
+use Apache::lonstudentsubmissions();
#######################################################
#######################################################
@@ -1028,6 +1031,11 @@
short_description =>
&mt('Display and analysis of submission times on assessments.'),
},
+ { internal_name => 'student_submission_reports',
+ name => &mt('Student Submission Reports'),
+ short_description =>
+ &mt('Prepare Excel spreadsheets of student submissions.'),
+ },
{ internal_name => 'correct_problems_plot',
name => &mt('Correct Problems Plot'),
short_description =>
@@ -1151,6 +1159,8 @@
&Apache::lonproblemanalysis::BuildProblemAnalysisPage($r,$c);
} elsif($GoToPage eq 'submissiontime_analysis') {
&Apache::lonsubmissiontimeanalysis::BuildSubmissionTimePage($r,$c);
+ } elsif($GoToPage eq 'student_submission_reports') {
+ &Apache::lonstudentsubmissions::BuildStudentSubmissionsPage($r,$c);
} elsif($GoToPage eq 'correct_problems_plot') {
&Apache::loncorrectproblemplot::BuildCorrectProblemsPage($r,$c);
} elsif($GoToPage eq 'student_assessment') {
Index: loncom/interface/statistics/lonproblemanalysis.pm
diff -u loncom/interface/statistics/lonproblemanalysis.pm:1.70 loncom/interface/statistics/lonproblemanalysis.pm:1.71
--- loncom/interface/statistics/lonproblemanalysis.pm:1.70 Wed Feb 18 14:16:55 2004
+++ loncom/interface/statistics/lonproblemanalysis.pm Thu Feb 19 15:17:01 2004
@@ -1,6 +1,6 @@
# The LearningOnline Network with CAPA
#
-# $Id: lonproblemanalysis.pm,v 1.70 2004/02/18 19:16:55 matthew Exp $
+# $Id: lonproblemanalysis.pm,v 1.71 2004/02/19 20:17:01 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -33,7 +33,8 @@
use Apache::loncoursedata();
use Apache::lonstatistics;
use Apache::lonlocal;
-use Apache::lonstathelpers;
+use Apache::lonstathelpers();
+use Apache::lonstudentsubmissions();
use HTML::Entities();
use Time::Local();
use Spreadsheet::WriteExcel();
@@ -146,7 +147,8 @@
$r->print('<h3>'.$resource->{'src'}.'</h3>');
$r->print(&Apache::lonstathelpers::render_resource($resource));
$r->rflush();
- my %Data = &get_problem_data($resource->{'src'});
+ my %Data = &Apache::lonstathelpers::get_problem_data
+ ($resource->{'src'});
my $ProblemData = $Data{$current_problem->{'part'}.
'.'.
$current_problem->{'respid'}};
@@ -160,8 +162,8 @@
\@Students);
} elsif ($current_problem->{'resptype'} eq 'numerical') {
# if (exists($ENV{'form.ExcelOutput'})) {
- &prepare_excel_output($r,$current_problem,
- $ProblemData,\@Students);
+ &Apache::lonstudentsubmissions::prepare_excel_output
+ ($r,$current_problem,$ProblemData,\@Students);
# } else {
# &NumericalResponseAnalysis($r,$current_problem,
# $ProblemData,\@Students);
@@ -185,136 +187,6 @@
#########################################################
#########################################################
##
-## 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
##
#########################################################
@@ -385,9 +257,8 @@
foreach my $student (@$Students) {
my $sname = $student->{'username'};
my $sdom = $student->{'domain'};
- my $answer = &analyze_problem_as_student($resource,
- $sname,$sdom,
- $partid,$respid);
+ my $answer = &Apache::lonstathelpers::analyze_problem_as_student
+ ($resource,$sname,$sdom,$partid,$respid);
&Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
&mt('last student'));
$student->{'answer'} = $answer;
@@ -1258,7 +1129,7 @@
}
$worksheet->write($rows_output,$cols_output++,$student);
$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++,$tries);
foreach my $foilid (@$Foils) {
@@ -1273,185 +1144,6 @@
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 {
my ($ORdata) = @_;
return if (! exists($ORdata->{'_Foils'}));
@@ -1762,84 +1454,6 @@
$RowData{$foilid}->{'_total'}++;
}
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;
Index: loncom/interface/statistics/lonstathelpers.pm
diff -u loncom/interface/statistics/lonstathelpers.pm:1.3 loncom/interface/statistics/lonstathelpers.pm:1.4
--- loncom/interface/statistics/lonstathelpers.pm:1.3 Fri Feb 13 13:34:40 2004
+++ loncom/interface/statistics/lonstathelpers.pm Thu Feb 19 15:17:01 2004
@@ -1,6 +1,6 @@
# The LearningOnline Network with CAPA
#
-# $Id: lonstathelpers.pm,v 1.3 2004/02/13 18:34:40 matthew Exp $
+# $Id: lonstathelpers.pm,v 1.4 2004/02/19 20:17:01 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -335,6 +335,322 @@
$next = $Resource[$curr_idx+1];
}
return ($prev,$curr,$next);
+}
+
+
+#####################################################
+#####################################################
+
+=pod
+
+=item analyze_problem_as_student
+
+Analyzes a homework problem for a student and returns the correct answer
+for the student. Attempts to put together an answer for problem types
+that do not natively support it.
+
+Inputs: $resource: a resource object
+ $sname, $sdom, $partid, $respid
+
+Returns: $answer
+
+=cut
+
+#####################################################
+#####################################################
+sub analyze_problem_as_student {
+ my ($resource,$sname,$sdom,$partid,$respid) = @_;
+ my $returnvalue;
+ 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 $prefix = $partid.'.'.$respid;
+ my $key = $prefix.'.answer';
+ if (exists($Answer{$key})) {
+ my $student_answer = $Answer{$key}->[0];
+ if (! defined($student_answer)) {
+ $student_answer = $Answer{$key}->[1];
+ }
+ $returnvalue = $student_answer;
+ } else {
+ if (exists($Answer{$prefix.'.shown'})) {
+ # The response has foils
+ my %values;
+ while (my ($k,$v) = each(%Answer)) {
+ next if ($k !~ /^$prefix\.foil\.(value|area)\.(.*)$/);
+ my $foilname = $2;
+ $values{$foilname}=$v;
+ }
+ foreach my $foil (@{$Answer{$prefix.'.shown'}}) {
+ if (ref($values{$foil}) eq 'ARRAY') {
+ $returnvalue.=&HTML::Entities::encode($foil).'='.
+ join(',',map {&HTML::Entities::encode($_)} @{$values{$foil}}).'&';
+ } else {
+ $returnvalue.=&HTML::Entities::encode($foil).'='.
+ &HTML::Entities::encode($values{$foil}).'&';
+ }
+ }
+ $returnvalue =~ s/ /\%20/g;
+ chop ($returnvalue);
+ }
+ }
+ return $returnvalue;
+}
+
+
+##
+## 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
+#
+######################################################################
+#-----------------------------------------------------------
+# 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 {
+ # 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;
+ 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;
+}
+
+###########################################################
+###########################################################
+
+=pod
+
+=item get_problem_data
+
+Returns a data structure describing the problem.
+
+Inputs: $url
+
+Returns: %Partdata
+
+=cut
+
+## 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 (1) {
+ &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;
}
####################################################
Index: loncom/interface/statistics/lonstudentsubmissions.pm
+++ loncom/interface/statistics/lonstudentsubmissions.pm
# The LearningOnline Network with CAPA
#
# $Id: lonstudentsubmissions.pm,v 1.1 2004/02/19 20:17:01 matthew 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
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#
package Apache::lonstudentsubmissions;
use strict;
use Apache::lonnet();
use Apache::loncommon();
use Apache::lonhtmlcommon();
use Apache::loncoursedata();
use Apache::lonstatistics;
use Apache::lonlocal;
use Apache::lonstathelpers;
use HTML::Entities();
use Time::Local();
use Spreadsheet::WriteExcel();
my @SubmitButtons = ({ name => 'PrevProblem',
text => 'Previous Problem' },
{ name => 'NextProblem',
text => 'Next Problem' },
{ name => 'break'},
{ name => 'ClearCache',
text => 'Clear Caches' },
{ name => 'updatecaches',
text => 'Update Student Data' },
{ name => 'SelectAnother',
text => 'Choose a different Problem' },
{ name => 'Generate',
text => 'Generate Spreadsheet'},
);
sub BuildStudentSubmissionsPage {
my ($r,$c)=@_;
#
my %Saveable_Parameters = ('Status' => 'scalar',
'Section' => 'array',
'NumPlots' => 'scalar',
);
&Apache::loncommon::store_course_settings('student_submissions',
\%Saveable_Parameters);
&Apache::loncommon::restore_course_settings('student_submissions',
\%Saveable_Parameters);
#
&Apache::lonstatistics::PrepareClasslist();
#
$r->print('<h2>'.&mt('Student Submissions Report').'</h2>');
$r->print(&CreateInterface());
#
my @Students = @Apache::lonstatistics::Students;
#
if (@Students < 1) {
$r->print('<h2>There are no students in the sections selected</h2>');
}
#
&Apache::loncoursedata::clear_internal_caches();
if (exists($ENV{'form.ClearCache'}) ||
exists($ENV{'form.updatecaches'}) ||
(exists($ENV{'form.firstanalysis'}) &&
$ENV{'form.firstanalysis'} ne 'no')) {
&Apache::lonstatistics::Gather_Full_Student_Data($r);
}
if (! exists($ENV{'form.firstanalysis'})) {
$r->print('<input type="hidden" name="firstanalysis" value="yes" />');
} else {
$r->print('<input type="hidden" name="firstanalysis" value="no" />');
}
$r->rflush();
#
if (exists($ENV{'form.problemchoice'}) &&
! exists($ENV{'form.SelectAnother'})) {
foreach my $button (@SubmitButtons) {
if ($button->{'name'} eq 'break') {
$r->print("<br />\n");
} else {
$r->print('<input type="submit" name="'.$button->{'name'}.'" '.
'value="'.&mt($button->{'text'}).'" />');
$r->print(' 'x5);
}
}
#
$r->print('<hr />');
$r->rflush();
#
# Determine which problem we are to analyze
my $current_problem = &Apache::lonstathelpers::get_target_from_id
($ENV{'form.problemchoice'});
#
my ($prev,$curr,$next) =
&Apache::lonstathelpers::get_prev_curr_next($current_problem,
'.',
'response',
);
if (exists($ENV{'form.PrevProblem'}) && defined($prev)) {
$current_problem = $prev;
} elsif (exists($ENV{'form.NextProblem'}) && defined($next)) {
$current_problem = $next;
} else {
$current_problem = $curr;
}
#
# Store the current problem choice and send it out in the form
$ENV{'form.problemchoice'} =
&Apache::lonstathelpers::make_target_id($current_problem);
$r->print('<input type="hidden" name="problemchoice" value="'.
$ENV{'form.problemchoice'}.'" />');
#
if (! defined($current_problem->{'resource'})) {
$r->print('resource is undefined');
} else {
my $resource = $current_problem->{'resource'};
$r->print('<h1>'.$resource->{'title'}.'</h1>');
$r->print('<h3>'.$resource->{'src'}.'</h3>');
$r->print(&Apache::lonstathelpers::render_resource($resource));
$r->rflush();
my %Data = &Apache::lonstathelpers::get_problem_data
($resource->{'src'});
my $ProblemData = $Data{$current_problem->{'part'}.
'.'.
$current_problem->{'respid'}};
&prepare_excel_output($r,$current_problem,
$ProblemData,\@Students);
}
$r->print('<hr />');
} else {
$r->print('<input type="submit" name="Generate" value="'.
&mt('Generate Spreadsheet').'" />');
$r->print(' 'x5);
$r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
$r->print(&Apache::lonstathelpers::ProblemSelector('.'));
}
}
#########################################################
#########################################################
##
## 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'} = &Apache::lonstathelpers::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()];
if ($row{'submission'} =~ m/^=/) {
# This will be interpreted as a formula. That is bad!
$row{'submission'} = " ".$row{'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");
}
sub GetStudentAnswers {
my ($r,$problem,$Students) = @_;
my %Answers;
my ($resource,$partid,$respid) = ($problem->{'resource'},
$problem->{'part'},
$problem->{'respid'});
# Open progress window
my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
($r,'Student Answer Compilation Status',
'Student Answer Compilation Progress', scalar(@$Students));
$r->print("<table>\n");
$r->rflush();
foreach my $student (@$Students) {
my $sname = $student->{'username'};
my $sdom = $student->{'domain'};
my $answer = &Apache::lonstathelpers::analyze_problem_as_student
($resource,$sname,$sdom,$partid,$respid);
&Apache::lonnet::logthis('answer = "'.$answer.'"');
&Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
&mt('last student'));
$student->{'answer'} = $answer;
}
$r->print("</table>\n");
$r->rflush();
# close progress window
&Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
return;
}
#########################################################
#########################################################
##
## Generic Interface Routines
##
#########################################################
#########################################################
sub CreateInterface {
##
## Environment variable initialization
my $Str = '';
$Str .= '<table cellspacing="5">'."\n";
$Str .= '<tr>';
$Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
$Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
$Str .= '<td align="center"> </td>';
$Str .= '</tr>'."\n";
##
##
$Str .= '<tr><td align="center">'."\n";
$Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
$Str .= '</td>';
#
$Str .= '<td align="center">';
$Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
$Str .= '</td>';
#
my $only_seq_with_assessments = sub {
my $s=shift;
if ($s->{'num_assess'} < 1) {
return 0;
} else {
return 1;
}
};
##
##
$Str .= '</tr>'."\n";
$Str .= '</table>'."\n";
#
# We do this to make sure the sequence information is initialized
&Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
$only_seq_with_assessments);
#
return $Str;
}
1;
__END__
--matthew1077221821--