--- loncom/interface/statistics/lonstathelpers.pm 2004/02/19 20:17:01 1.4
+++ loncom/interface/statistics/lonstathelpers.pm 2004/03/31 05:24:00 1.10
@@ -1,6 +1,6 @@
# The LearningOnline Network with CAPA
#
-# $Id: lonstathelpers.pm,v 1.4 2004/02/19 20:17:01 matthew Exp $
+# $Id: lonstathelpers.pm,v 1.10 2004/03/31 05:24:00 albertel Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -59,6 +59,8 @@ use Apache::lonlocal;
use HTML::Entities();
use Time::Local();
use Spreadsheet::WriteExcel();
+use GDBM_File;
+use Storable qw(freeze thaw);
####################################################
####################################################
@@ -145,7 +147,7 @@ sub ProblemSelector {
$resptype.'
'.
''.$title.' ';
# ''.$resptype.' '.$res->{'title'}.' ';
- if ($partdata->{'option'} > 1) {
+ if (scalar(@{$partdata->{'ResponseIds'}}) > 1) {
$seq_str .= &mt('response').' '.$respid;
}
$seq_str .= " | \n";
@@ -343,6 +345,60 @@ sub get_prev_curr_next {
=pod
+=item GetStudentAnswers($r,$problem,$Students)
+
+Determines the correct answer for a set of students on a given problem.
+The students answers are stored in the student hashes pointed to by the
+array @$Students under the key 'answer'.
+
+Inputs: $r
+$problem: hash reference containing the keys 'resource', 'part', and 'respid'.
+$Students: reference to array containing student hashes (need 'username',
+ 'domain').
+
+Returns: nothing
+
+=cut
+
+#####################################################
+#####################################################
+sub GetStudentAnswers {
+ my ($r,$problem,$Students) = @_;
+ my $c = $r->connection();
+ my %Answers;
+ my ($resource,$partid,$respid) = ($problem->{'resource'},
+ $problem->{'part'},
+ $problem->{'respid'});
+ # Read in the cache (if it exists) before we start timing things.
+ &Apache::lonstathelpers::ensure_proper_cache($resource->{'symb'});
+ # Open progress window
+ my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
+ ($r,'Student Answer Compilation Status',
+ 'Student Answer Compilation Progress', scalar(@$Students));
+ $r->rflush();
+ foreach my $student (@$Students) {
+ last if ($c->aborted());
+ my $sname = $student->{'username'};
+ my $sdom = $student->{'domain'};
+ 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;
+ }
+ &Apache::lonstathelpers::write_answer_cache();
+ return if ($c->aborted());
+ $r->rflush();
+ # close progress window
+ &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+ return;
+}
+
+#####################################################
+#####################################################
+
+=pod
+
=item analyze_problem_as_student
Analyzes a homework problem for a student and returns the correct answer
@@ -354,6 +410,12 @@ Inputs: $resource: a resource object
Returns: $answer
+If $partid and $respid are specified, $answer is simply a scalar containing
+the correct answer for the response.
+
+If $partid or $respid are undefined, $answer will be a hash reference with
+keys $partid.'.'.$respid.'.answer'.
+
=cut
#####################################################
@@ -363,6 +425,10 @@ sub analyze_problem_as_student {
my $returnvalue;
my $url = $resource->{'src'};
my $symb = $resource->{'symb'};
+ my $answer = &get_from_answer_cache($sname,$sdom,$symb,$partid,$respid);
+ if (defined($answer)) {
+ return($answer);
+ }
my $courseid = $ENV{'request.course.id'};
my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze',
'grade_domain' => $sdom,
@@ -371,8 +437,30 @@ sub analyze_problem_as_student {
'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';
+ #
+ undef($answer);
+ foreach my $partid (@{$resource->{'parts'}}) {
+ my $partdata = $resource->{'partdata'}->{$partid};
+ foreach my $respid (@{$partdata->{'ResponseIds'}}) {
+ my $prefix = $partid.'.'.$respid;
+ my $key = $prefix.'.answer';
+ $answer->{$partid}->{$respid} = &get_answer($prefix,$key,%Answer);
+ }
+ }
+ &store_answer($sname,$sdom,$symb,undef,undef,$answer);
+ if (! defined($partid)) {
+ $returnvalue = $answer;
+ } elsif (! defined($respid)) {
+ $returnvalue = $answer->{$partid};
+ } else {
+ $returnvalue = $answer->{$partid}->{$respid};
+ }
+ return $returnvalue;
+}
+
+sub get_answer {
+ my ($prefix,$key,%Answer) = @_;
+ my $returnvalue;
if (exists($Answer{$key})) {
my $student_answer = $Answer{$key}->[0];
if (! defined($student_answer)) {
@@ -390,11 +478,11 @@ sub analyze_problem_as_student {
}
foreach my $foil (@{$Answer{$prefix.'.shown'}}) {
if (ref($values{$foil}) eq 'ARRAY') {
- $returnvalue.=&HTML::Entities::encode($foil).'='.
- join(',',map {&HTML::Entities::encode($_)} @{$values{$foil}}).'&';
+ $returnvalue.=&HTML::Entities::encode($foil,'<>&"').'='.
+ join(',',map {&HTML::Entities::encode($_,'<>&"')} @{$values{$foil}}).'&';
} else {
- $returnvalue.=&HTML::Entities::encode($foil).'='.
- &HTML::Entities::encode($values{$foil}).'&';
+ $returnvalue.=&HTML::Entities::encode($foil,'<>&"').'='.
+ &HTML::Entities::encode($values{$foil},'<>&"').'&';
}
}
$returnvalue =~ s/ /\%20/g;
@@ -405,6 +493,190 @@ sub analyze_problem_as_student {
}
+#####################################################
+#####################################################
+
+=pod
+
+=item Caching routines
+
+=over 4
+
+=item &load_answer_cache($symb)
+
+Loads the cache for the given symb into memory from disk.
+Requires the cache filename be set.
+Only should be called by &ensure_proper_cache.
+
+=cut
+
+#####################################################
+#####################################################
+{
+ my $cache_filename = undef;
+ my $current_symb = undef;
+ my %cache;
+
+sub load_answer_cache {
+ my ($symb) = @_;
+ return if (! defined($cache_filename));
+ if (! defined($current_symb) || $current_symb ne $symb) {
+ undef(%cache);
+ my $storedstring;
+ my %cache_db;
+ if (tie(%cache_db,'GDBM_File',$cache_filename,&GDBM_READER(),0640)) {
+ $storedstring = $cache_db{&Apache::lonnet::escape($symb)};
+ untie(%cache_db);
+ }
+ if (defined($storedstring)) {
+ %cache = %{thaw($storedstring)};
+ }
+ }
+ return;
+}
+
+#####################################################
+#####################################################
+
+=pod
+
+=item &get_from_answer_cache($sname,$sdom,$symb,$partid,$respid)
+
+Returns the appropriate data from the cache, or undef if no data exists.
+If $respid is undefined, a hash ref containing the answers for the given
+$partid is returned. If $partid is undefined, a hash ref containing answers
+for all of the parts is returned.
+
+=cut
+
+#####################################################
+#####################################################
+sub get_from_answer_cache {
+ my ($sname,$sdom,$symb,$partid,$respid) = @_;
+ &ensure_proper_cache($symb);
+ my $returnvalue;
+ if (exists($cache{$sname.':'.$sdom}) &&
+ ref($cache{$sname.':'.$sdom}) eq 'HASH') {
+ if (defined($partid) &&
+ exists($cache{$sname.':'.$sdom}->{$partid})) {
+ if (defined($respid) &&
+ exists($cache{$sname.':'.$sdom}->{$partid}->{$respid})) {
+ $returnvalue = $cache{$sname.':'.$sdom}->{$partid}->{$respid};
+ } else {
+ $returnvalue = $cache{$sname.':'.$sdom}->{$partid};
+ }
+ } else {
+ $returnvalue = $cache{$sname.':'.$sdom};
+ }
+ } else {
+ $returnvalue = undef;
+ }
+ return $returnvalue;
+}
+
+#####################################################
+#####################################################
+
+=pod
+
+=item &write_answer_cache($symb)
+
+Writes the in memory cache to disk so that it can be read in with
+&load_answer_cache($symb).
+
+=cut
+
+#####################################################
+#####################################################
+sub write_answer_cache {
+ return if (! defined($current_symb) || ! defined($cache_filename));
+ my %cache_db;
+ my $key = &Apache::lonnet::escape($current_symb);
+ if (tie(%cache_db,'GDBM_File',$cache_filename,&GDBM_WRCREAT(),0640)) {
+ my $storestring = freeze(\%cache);
+ $cache_db{$key}=$storestring;
+ $cache_db{$key.'.time'}=time;
+ untie(%cache_db);
+ }
+ undef(%cache);
+ undef($current_symb);
+ undef($cache_filename);
+ return;
+}
+
+#####################################################
+#####################################################
+
+=pod
+
+=item &ensure_proper_cache($symb)
+
+Called to make sure we have the proper cache set up. This is called
+prior to every answer lookup.
+
+=cut
+
+#####################################################
+#####################################################
+sub ensure_proper_cache {
+ my ($symb) = @_;
+ my $cid = $ENV{'request.course.id'};
+ my $new_filename = '/home/httpd/perl/tmp/'.
+ 'problemanalsysis_'.$cid.'answer_cache.db';
+ if (! defined($cache_filename) ||
+ $cache_filename ne $new_filename ||
+ ! defined($current_symb) ||
+ $current_symb ne $symb) {
+ $cache_filename = $new_filename;
+ # Notice: $current_symb is not set to $symb until after the cache is
+ # loaded. This is what tells &load_answer_cache to load in a new
+ # symb cache.
+ &load_answer_cache($symb);
+ $current_symb = $symb;
+ }
+}
+
+#####################################################
+#####################################################
+
+=pod
+
+=item &store_answer($sname,$sdom,$symb,$partid,$respid,$dataset)
+
+Stores the answer data in the in memory cache.
+
+=cut
+
+#####################################################
+#####################################################
+sub store_answer {
+ my ($sname,$sdom,$symb,$partid,$respid,$dataset) = @_;
+ return if ($symb ne $current_symb);
+ if (defined($partid)) {
+ if (defined($respid)) {
+ $cache{$sname.':'.$sdom}->{$partid}->{$respid} = $dataset;
+ } else {
+ $cache{$sname.':'.$sdom}->{$partid} = $dataset;
+ }
+ } else {
+ $cache{$sname.':'.$sdom}=$dataset;
+ }
+ return;
+}
+
+}
+#####################################################
+#####################################################
+
+=pod
+
+=back
+
+=cut
+
+#####################################################
+#####################################################
+
##
## The following is copied from datecalc1.pl, part of the
## Spreadsheet::WriteExcel CPAN module.
@@ -612,7 +884,7 @@ sub get_problem_data {
while (my($key,$value) = each(%Answer)) {
#
# Logging code:
- if (1) {
+ if (0) {
&Apache::lonnet::logthis($part.' got key "'.$key.'"');
if (ref($value) eq 'ARRAY') {
&Apache::lonnet::logthis(' @'.join(',',@$value));
@@ -654,6 +926,82 @@ sub get_problem_data {
}
####################################################
+####################################################
+
+=pod
+
+=item &limit_by_time()
+
+=cut
+
+####################################################
+####################################################
+sub limit_by_time_form {
+ my $Starttime_form = '';
+ my $starttime = &Apache::lonhtmlcommon::get_date_from_form
+ ('limitby_startdate');
+ my $endtime = &Apache::lonhtmlcommon::get_date_from_form
+ ('limitby_enddate');
+ if (! defined($endtime)) {
+ $endtime = time;
+ }
+ if (! defined($starttime)) {
+ $starttime = $endtime - 60*60*24*7;
+ }
+ my $state;
+ if (&limit_by_time()) {
+ $state = '';
+ } else {
+ $state = 'disabled';
+ }
+ my $startdateform = &Apache::lonhtmlcommon::date_setter
+ ('Statistics','limitby_startdate',$starttime,undef,undef,$state);
+ my $enddateform = &Apache::lonhtmlcommon::date_setter
+ ('Statistics','limitby_enddate',$endtime,undef,undef,$state);
+ my $Str;
+ $Str .= '';
+ $Str .= '';
+ return $Str;
+}
+
+sub limit_by_time {
+ if (exists($ENV{'form.limit_by_time'}) &&
+ $ENV{'form.limit_by_time'} ne '' ) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+sub get_time_limits {
+ my $starttime = &Apache::lonhtmlcommon::get_date_from_form
+ ('limitby_startdate');
+ my $endtime = &Apache::lonhtmlcommon::get_date_from_form
+ ('limitby_enddate');
+ return ($starttime,$endtime);
+}
+
+####################################################
####################################################
=pod