--- loncom/homework/grades.pm 2008/12/31 21:08:09 1.528.2.6
+++ loncom/homework/grades.pm 2008/12/05 10:23:50 1.532
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.528.2.6 2008/12/31 21:08:09 raeburn Exp $
+# $Id: grades.pm,v 1.532 2008/12/05 10:23:50 bisitz Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -26,6 +26,8 @@
# http://www.lon-capa.org/
#
+
+
package Apache::grades;
use strict;
use Apache::style;
@@ -58,46 +60,6 @@ my $ssi_error_resource;
my $ssi_error_message;
-# Do an ssi with retries:
-# While I'd love to factor out this with the vesrion in lonprintout,
-# that would either require a data coupling between modules, which I refuse to perpetuate
-# (there's quite enough of that already), or would require the invention of another infrastructure
-# I'm not quite ready to invent (e.g. an ssi_with_retry object).
-#
-# At least the logic that drives this has been pulled out into loncommon.
-
-
-#
-# ssi_with_retries - Does the server side include of a resource.
-# if the ssi call returns an error we'll retry it up to
-# the number of times requested by the caller.
-# If we still have a proble, no text is appended to the
-# output and we set some global variables.
-# to indicate to the caller an SSI error occurred.
-# All of this is supposed to deal with the issues described
-# in LonCAPA BZ 5631 see:
-# http://bugs.lon-capa.org/show_bug.cgi?id=5631
-# by informing the user that this happened.
-#
-# Parameters:
-# resource - The resource to include. This is passed directly, without
-# interpretation to lonnet::ssi.
-# form - The form hash parameters that guide the interpretation of the resource
-#
-# retries - Number of retries allowed before giving up completely.
-# Returns:
-# On success, returns the rendered resource identified by the resource parameter.
-# Side Effects:
-# The following global variables can be set:
-# ssi_error - If an unrecoverable error occurred this becomes true.
-# It is up to the caller to initialize this to false
-# if desired.
-# ssi_error_resource - If an unrecoverable error occurred, this is the value
-# of the resource that could not be rendered by the ssi
-# call.
-# ssi_error_message - The error string fetched from the ssi response
-# in the event of an error.
-#
sub ssi_with_retries {
my ($resource, $retries, %form) = @_;
my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
@@ -2358,7 +2320,7 @@ sub get_last_submission {
$$returnhash{$version.':keys'}))) {
$lasthash{$key}=$$returnhash{$version.':'.$key};
$timestamp =
- &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
+ scalar(localtime($$returnhash{$version.':timestamp'}));
}
}
foreach my $key (keys(%lasthash)) {
@@ -2796,10 +2758,8 @@ sub handback_files {
$newflg.'_'.$part_resp.'_returndoc'.$file_counter,
$save_file_name);
if ($result !~ m|^/uploaded/|) {
- $request->print(' ');
- if ($scancode eq '') {
- $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2].',
- $uname.':'.$udom,$scan_record->{'scantron.ID'}));
- } else {
- $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2] and CODE: [_3].',
- $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
- }
- $r->print('
'.
- &mt('An error occurred ([_1]) while trying to upload [_2].',
- $result,$newflg.'_'.$part_resp.'_returndoc'.$file_counter).
- '');
+ $request->print('An error occurred ('.$result.
+ ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'
');
} else {
# mark the file as read only
my @files = ($save_file_name);
@@ -6613,14 +6573,7 @@ sub scantron_validate_sequence {
return (0,$currentphase+1);
}
-=pod
-
-=item scantron_validate_ID
- Validates all scanlines in the selected file to not have any
- invalid or underspecified student IDs
-
-=cut
sub scantron_validate_ID {
my ($r,$currentphase) = @_;
@@ -6686,35 +6639,6 @@ sub scantron_validate_ID {
return (0,$currentphase+1);
}
-=pod
-
-=item scantron_get_correction
-
- Builds the interface screen to interact with the operator to fix a
- specific error condition in a specific scanline
-
- Arguments:
- $r - Apache request object
- $i - number of the current scanline
- $scan_record - hash ref as returned from &scantron_parse_scanline()
- $scan_config - hash ref as returned from &get_scantron_config()
- $line - full contents of the current scanline
- $error - error condition, valid values are
- 'incorrectCODE', 'duplicateCODE',
- 'doublebubble', 'missingbubble',
- 'duplicateID', 'incorrectID'
- $arg - extra information needed
- For errors:
- - duplicateID - paper number that this studentID was seen before on
- - duplicateCODE - array ref of the paper numbers this CODE was
- seen on before
- - incorrectCODE - current incorrect CODE
- - doublebubble - array ref of the bubble lines that have double
- bubble errors
- - missingbubble - array ref of the bubble lines that have missing
- bubble errors
-
-=cut
sub scantron_get_correction {
my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
@@ -7285,25 +7209,6 @@ sub scantron_validate_doublebubble {
return (0,$currentphase+1);
}
-=pod
-
-=item scantron_get_maxbubble
-
- Returns the maximum number of bubble lines that are expected to
- occur. Does this by walking the selected sequence rendering the
- resource and then checking &Apache::lonxml::get_problem_counter()
- for what the current value of the problem counter is.
-
- Caches the results to $env{'form.scantron_maxbubble'},
- $env{'form.scantron.bubble_lines.n'},
- $env{'form.scantron.first_bubble_line.n'} and
- $env{"form.scantron.sub_bubblelines.n"}
- which are the total number of bubble, lines, the number of bubble
- lines for response n and number of the first bubble line for response n,
- and a comma separated list of numbers of bubble lines for sub-questions
- (for optionresponse, matchresponse, and rankresponse items), for response n.
-
-=cut
sub scantron_get_maxbubble {
if (defined($env{'form.scantron_maxbubble'}) &&
@@ -7333,84 +7238,35 @@ sub scantron_get_maxbubble {
my $response_number = 0;
my $bubble_line = 0;
foreach my $resource (@resources) {
- my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom);
- if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
- foreach my $part_id (@{$parts}) {
-
- my $lines;
-
- # TODO - make this a persistent hash not an array.
-
- # optionresponse, matchresponse and rankresponse type items
- # render as separate sub-questions in exam mode.
- if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
- ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
- ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
- my ($numbub,$numshown);
- if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
- if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
- $numbub = scalar(@{$analysis->{$part_id.'.options'}});
- }
- } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
- if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
- $numbub = scalar(@{$analysis->{$part_id.'.items'}});
- }
- } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
- if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
- $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
- }
- }
- if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
- $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
- }
- my $bubbles_per_line = 10;
- my $inner_bubble_lines = int($numbub/$bubbles_per_line);
- if (($numbub % $bubbles_per_line) != 0) {
- $inner_bubble_lines++;
- }
- for (my $i=0; $i<$numshown; $i++) {
- $subdivided_bubble_lines{$response_number} .=
- $inner_bubble_lines.',';
- }
- $subdivided_bubble_lines{$response_number} =~ s/,$//;
- $lines = $numshown * $inner_bubble_lines;
- } else {
- $lines = $analysis->{"$part_id.bubble_lines"};
- }
+ my $symb = $resource->symb();
- $first_bubble_line{$response_number} = $bubble_line;
- $bubble_lines_per_response{$response_number} = $lines;
- $responsetype_per_response{$response_number} =
- $analysis->{$part_id.'.type'};
- $response_number++;
+ my (@parts,@allparts,@possible_parts);
- $bubble_line += $lines;
- $total_lines += $lines;
- }
+ # Need to retrieve part IDs and response IDs because essayresponse,
+ # reactionresponse and organicresponse items are not included in
+ # $analysis{'parts'} from lonnet::ssi.
+ if (ref($resource->parts()) eq 'ARRAY') {
+ foreach my $part (@{$resource->parts()}) {
+ if (!&Apache::loncommon::check_if_partid_hidden($part,$symb,$udom,$uname)) {
+ my @resp_ids = $resource->responseIds($part);
+ foreach my $id (@resp_ids) {
+ my $part_id = $part.'.'.$id;
+ push(@possible_parts,$part_id);
+ }
+ }
+ }
}
- }
- &Apache::lonnet::delenv('scantron\.');
- &save_bubble_lines();
- $env{'form.scantron_maxbubble'} =
- $total_lines;
- return $env{'form.scantron_maxbubble'};
-}
-
-sub scantron_partids_tograde {
- my ($resource,$cid,$uname,$udom) = @_;
- my (%analysis,@parts);
-
- if (ref($resource)) {
- my $symb = $resource->symb();
my $result=&ssi_with_retries($resource->src(), $ssi_retries,
('symb' => $symb,
'grade_target' => 'analyze',
'grade_courseid' => $cid,
'grade_domain' => $udom,
'grade_username' => $uname));
- my (undef, $an) = split(/_HASH_REF__/,$result, 2);
- %analysis = &Apache::lonnet::str2hash($an);
+ my (undef, $an) =
+ split(/_HASH_REF__/,$result, 2);
+
+ my %analysis = &Apache::lonnet::str2hash($an);
if (ref($analysis{'parts'}) eq 'ARRAY') {
foreach my $part (@{$analysis{'parts'}}) {
@@ -7420,19 +7276,81 @@ sub scantron_partids_tograde {
}
}
}
- }
- return (\%analysis,\@parts);
-}
+ # Add part_ids for any essayresponse, reactionresponse or
+ # organicresponse items.
+ foreach my $part_id (@possible_parts) {
+ if (grep(/^\Q$part_id\E$/,@parts)) {
+ push(@allparts,$part_id);
+ } else {
+ if (($analysis{$part_id.'.type'} eq 'essayresponse') ||
+ ($analysis{$part_id.'.type'} eq 'reactionresponse') ||
+ ($analysis{$part_id.'.type'} eq 'organicresponse')) {
+ push(@allparts,$part_id);
+ }
+ }
+ }
-=pod
+ foreach my $part_id (@allparts) {
+ my $lines;
-=item scantron_validate_missingbubbles
+ # TODO - make this a persistent hash not an array.
- Validates all scanlines in the selected file to not have any
- answers that don't have bubbles that have not been verified
- to be bubble free.
+ # optionresponse, matchresponse and rankresponse type items
+ # render as separate sub-questions in exam mode.
+ if (($analysis{$part_id.'.type'} eq 'optionresponse') ||
+ ($analysis{$part_id.'.type'} eq 'matchresponse') ||
+ ($analysis{$part_id.'.type'} eq 'rankresponse')) {
+ my ($numbub,$numshown);
+ if ($analysis{$part_id.'.type'} eq 'optionresponse') {
+ if (ref($analysis{$part_id.'.options'}) eq 'ARRAY') {
+ $numbub = scalar(@{$analysis{$part_id.'.options'}});
+ }
+ } elsif ($analysis{$part_id.'.type'} eq 'matchresponse') {
+ if (ref($analysis{$part_id.'.items'}) eq 'ARRAY') {
+ $numbub = scalar(@{$analysis{$part_id.'.items'}});
+ }
+ } elsif ($analysis{$part_id.'.type'} eq 'rankresponse') {
+ if (ref($analysis{$part_id.'.foils'}) eq 'ARRAY') {
+ $numbub = scalar(@{$analysis{$part_id.'.foils'}});
+ }
+ }
+ if (ref($analysis{$part_id.'.shown'}) eq 'ARRAY') {
+ $numshown = scalar(@{$analysis{$part_id.'.shown'}});
+ }
+ my $bubbles_per_line = 10;
+ my $inner_bubble_lines = int($numbub/$bubbles_per_line);
+ if (($numbub % $bubbles_per_line) != 0) {
+ $inner_bubble_lines++;
+ }
+ for (my $i=0; $i<$numshown; $i++) {
+ $subdivided_bubble_lines{$response_number} .=
+ $inner_bubble_lines.',';
+ }
+ $subdivided_bubble_lines{$response_number} =~ s/,$//;
+ $lines = $numshown * $inner_bubble_lines;
+ } else {
+ $lines = $analysis{"$part_id.bubble_lines"};
+ }
+
+ $first_bubble_line{$response_number} = $bubble_line;
+ $bubble_lines_per_response{$response_number} = $lines;
+ $responsetype_per_response{$response_number} =
+ $analysis{$part_id.'.type'};
+ $response_number++;
+
+ $bubble_line += $lines;
+ $total_lines += $lines;
+ }
+
+ }
+ &Apache::lonnet::delenv('scantron\.');
+
+ &save_bubble_lines();
+ $env{'form.scantron_maxbubble'} =
+ $total_lines;
+ return $env{'form.scantron_maxbubble'};
+}
-=cut
sub scantron_validate_missingbubbles {
my ($r,$currentphase) = @_;
@@ -7487,29 +7405,6 @@ sub scantron_validate_missingbubbles {
return (0,$currentphase+1);
}
-=pod
-
-=item scantron_process_students
-
- Routine that does the actual grading of the bubble sheet information.
-
- The parsed scanline hash is added to %env
-
- Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
- foreach resource , with the form data of
-
- 'submitted' =>'scantron'
- 'grade_target' =>'grade',
- 'grade_username'=> username of student
- 'grade_domain' => domain of student
- 'grade_courseid'=> of course
- 'grade_symb' => symb of resource to grade
-
- This triggers a grading pass. The problem grading code takes care
- of converting the bubbled letter information (now in %env) into a
- valid submission.
-
-=cut
sub scantron_process_students {
my ($r) = @_;
@@ -7528,14 +7423,6 @@ sub scantron_process_students {
my $navmap=Apache::lonnavmaps::navmap->new();
my $map=$navmap->getResourceByUrl($sequence);
my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
-
- my ($uname,$udom,%partids_by_symb);
- foreach my $resource (@resources) {
- my $ressymb = $resource->symb();
- my ($analysis,$parts) =
- &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom);
- $partids_by_symb{$ressymb} = $parts;
- }
# $r->print("geto ".scalar(@resources)."
");
my $result= <
');
my $start=&Time::HiRes::time();
my $i=-1;
- my $started;
+ my ($uname,$udom,$started);
&scantron_get_maxbubble(); # Need the bubble lines array to parse.
@@ -7573,9 +7459,6 @@ SCANTRONFORM
return ''; # Dunno why the other returns return '' rather than just returning.
}
- my %lettdig = &letter_to_digits();
- my $numletts = scalar(keys(%lettdig));
-
while ($i<$scanlines->{'count'}) {
($uname,$udom)=('','');
$i++;
@@ -7607,80 +7490,36 @@ SCANTRONFORM
if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
&scantron_putfile($scanlines,$scan_data);
}
+
+ my $i=0;
+ foreach my $resource (@resources) {
+ $i++;
+ my %form=('submitted' =>'scantron',
+ 'grade_target' =>'grade',
+ 'grade_username'=>$uname,
+ 'grade_domain' =>$udom,
+ 'grade_courseid'=>$env{'request.course.id'},
+ 'grade_symb' =>$resource->symb());
+ if (exists($scan_record->{'scantron.CODE'})
+ &&
+ &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {
+ $form{'CODE'}=$scan_record->{'scantron.CODE'};
+ } else {
+ $form{'CODE'}='';
+ }
+ my $result=&ssi_with_retries($resource->src(), $ssi_retries, %form);
+ if ($ssi_error) {
+ $ssi_error = 0; # So end of handler error message does not trigger.
+ $r->print("");
+ &ssi_print_error($r);
+ $r->print(&show_grading_menu_form($symb));
+ &Apache::lonnet::remove_lock($lock);
+ return ''; # Why return ''? Beats me.
+ }
- my $scancode;
- if ((exists($scan_record->{'scantron.CODE'})) &&
- (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
- $scancode = $scan_record->{'scantron.CODE'};
- } else {
- $scancode = '';
- }
-
- if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
- @resources) eq 'ssi_error') {
- $ssi_error = 0; # So end of handler error message does not trigger.
- $r->print("");
- &ssi_print_error($r);
- $r->print(&show_grading_menu_form($symb));
- &Apache::lonnet::remove_lock($lock);
- return ''; # Why return ''? Beats me.
- }
-
+ if (&Apache::loncommon::connection_aborted($r)) { last; }
+ }
$completedstudents{$uname}={'line'=>$line};
- if ($env{'form.verifyrecord'}) {
- my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
- my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
- chomp($studentdata);
- $studentdata =~ s/\r$//;
- my $studentrecord = '';
- my $counter = -1;
- foreach my $resource (@resources) {
- ($counter,my $recording) =
- &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
- $counter,$studentdata,\%partids_by_symb,
- \%scantron_config,\%lettdig,$numletts);
- $studentrecord .= $recording;
- }
- if ($studentrecord ne $studentdata) {
- $counter = -1;
- $studentrecord = '';
- foreach my $resource (@resources) {
- ($counter,my $recording) =
- &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
- $counter,$studentdata,\%partids_by_symb,
- \%scantron_config,\%lettdig,$numletts);
- $studentrecord .= $recording;
- }
- if ($studentrecord ne $studentdata) {
- $r->print('
'.&Apache::loncommon::start_data_table()."\n".
- &Apache::loncommon::start_data_table_header_row()."\n".
- ''.&mt('Source').' '.&mt('Bubbled responses').' '.
- &Apache::loncommon::end_data_table_header_row()."\n".
- &Apache::loncommon::start_data_table_row().
- ''.&mt('Bubble Sheet').' '.
- ''.$studentdata.' '.
- &Apache::loncommon::end_data_table_row().
- &Apache::loncommon::start_data_table_row().
- 'Stored submissions '.
- ''.$studentrecord.' '."\n".
- &Apache::loncommon::end_data_table_row().
- &Apache::loncommon::end_data_table().'