';
my %newrecord=();
@@ -4597,10 +4602,10 @@ sub updateGradeByPage {
}
my $display_part=&get_display_part($partid,$curRes->symb());
my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
- $displayPts[0].=' Part: '.$display_part.' = '.
+ $displayPts[0].=' '.&mt('Part').': '.$display_part.' = '.
(($oldstatus eq 'excused') ? 'excused' : $oldpts).
' ';
- $displayPts[1].=' Part: '.$display_part.' = '.
+ $displayPts[1].=' '.&mt('Part').': '.$display_part.' = '.
(($score eq 'excused') ? 'excused' : $newpts).
' ';
$question++;
@@ -4649,9 +4654,9 @@ sub updateGradeByPage {
$studentTable.=&Apache::loncommon::end_data_table();
$studentTable.=&show_grading_menu_form($env{'form.symb'});
- my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
- 'The scores were changed for '.
- $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));
+ my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
+ &mt('The scores were changed for [quant,_1,problem].',
+ $changeflag));
$request->print($grademsg.$studentTable);
return '';
@@ -4982,11 +4987,11 @@ sub scantron_CODElist {
=cut
sub scantron_CODEunique {
- my $result='
+ my $result=''.&mt('Yes').'
-
+ '.&mt('No').' ';
@@ -5145,8 +5150,37 @@ sub scantron_selectphase {
');
&Apache::lonpickcode::code_list($r,2);
+
+ $r->print('
');
$r->print($grading_menu_button);
- return
+ return;
}
=pod
@@ -6180,16 +6214,21 @@ sub scantron_validate_file {
}
}
if (!$stop) {
- my $warning=&scantron_warning_screen('Start Grading');
- $r->print(&mt('Validation process complete.').'
-'.$warning.'
-
-
-');
-
+ my $warning=&scantron_warning_screen('Start Grading');
+ $r->print(&mt('Validation process complete.').' '.
+ $warning.
+ &mt('Perform verification for each student after storage of submissions?').
+ ' '.
+ ''.&mt('Yes').''.
+ (' 'x3).''.
+ ''.&mt('No').
+ ' '.
+ &mt('Grading will take longer if you use verification.').' '. &mt("Alternatively, the 'Review scantron data' utility (see grading menu) can be used for all students after grading is complete.").'
'.
+ ''.
+ ''."\n");
} else {
- $r->print('');
- $r->print("");
+ $r->print('');
+ $r->print("");
}
if ($stop) {
if ($validate_phases[$currentphase] eq 'sequence') {
@@ -6818,7 +6857,7 @@ ENDSCRIPT
foreach my $question (@{$arg}) {
my @linenums = &prompt_for_corrections($r,$question,$scan_config,
$scan_record, $error);
- push (@lines_to_correct,@linenums);
+ push(@lines_to_correct,@linenums);
}
$r->print(&verify_bubbles_checked(@lines_to_correct));
} elsif ($error eq 'missingbubble') {
@@ -6838,7 +6877,7 @@ ENDSCRIPT
foreach my $question (@{$arg}) {
my @linenums = &prompt_for_corrections($r,$question,$scan_config,
$scan_record, $error);
- push (@lines_to_correct,@linenums);
+ push(@lines_to_correct,@linenums);
}
$r->print(&verify_bubbles_checked(@lines_to_correct));
} else {
@@ -6996,7 +7035,7 @@ sub prompt_for_corrections {
my $selected = $$scan_record{"scantron.$current_line.answer"};
&scantron_bubble_selector($r,$scan_config,$current_line,
$questionnum,$error,split('', $selected));
- push (@linenums,$current_line);
+ push(@linenums,$current_line);
$current_line++;
}
if ($lines > 1) {
@@ -7212,7 +7251,7 @@ sub scantron_validate_CODE {
$line,'duplicateCODE',$usedCODEs{$CODE});
return(1,$currentphase);
}
- push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
+ push(@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
}
return (0,$currentphase+1);
}
@@ -7299,33 +7338,84 @@ sub scantron_get_maxbubble {
my $response_number = 0;
my $bubble_line = 0;
foreach my $resource (@resources) {
- my $symb = $resource->symb();
- # Need to retrieve part IDs and response IDs because essayresponse,
- # reactionresponse and organicresponse items are not included in
- # $analysis{'parts'} from lonnet::ssi.
- my %possible_part_ids;
- 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) {
- $possible_part_ids{$part.'.'.$id} = 1;
+ 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"};
+ }
+
+ $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;
+ }
}
- 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);
+ }
+ &Apache::lonnet::delenv('scantron\.');
+
+ &save_bubble_lines();
+ $env{'form.scantron_maxbubble'} =
+ $total_lines;
+ return $env{'form.scantron_maxbubble'};
+}
- my @parts;
+sub scantron_partids_tograde {
+ my ($resource,$cid,$uname,$udom) = @_;
+ my (%analysis,@parts);
- my %analysis = &Apache::lonnet::str2hash($an);
+ 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);
if (ref($analysis{'parts'}) eq 'ARRAY') {
foreach my $part (@{$analysis{'parts'}}) {
@@ -7335,73 +7425,8 @@ sub scantron_get_maxbubble {
}
}
}
- # Add part_ids for any essayresponse items.
- foreach my $part_id (keys(%possible_part_ids)) {
- if (($analysis{$part_id.'.type'} eq 'essayresponse') ||
- ($analysis{$part_id.'.type'} eq 'reactionresponse') ||
- ($analysis{$part_id.'.type'} eq 'organicresponse')) {
- if (!grep(/^\Q$part_id\E$/,@parts)) {
- push (@parts,$part_id);
- }
- }
- }
-
- foreach my $part_id (@parts) {
- my $lines = $analysis{"$part_id.bubble_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($numshown/$bubbles_per_line);
- if (($numshown % $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/,$//;
- }
-
- $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'};
+ return (\%analysis,\@parts);
}
=pod
@@ -7508,6 +7533,14 @@ 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= <
@@ -7517,7 +7550,7 @@ SCANTRONFORM
$r->print($result);
my @delayqueue;
- my %completedstudents;
+ my (%completedstudents,,%scandata);
my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));
my $count=&get_todo_count($scanlines,$scan_data);
@@ -7526,9 +7559,10 @@ SCANTRONFORM
'inline',undef,'scantronupload');
&Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
'Processing first student');
+ $r->print(' ');
my $start=&Time::HiRes::time();
my $i=-1;
- my ($uname,$udom,$started);
+ my $started;
&scantron_get_maxbubble(); # Need the bubble lines array to parse.
@@ -7544,6 +7578,9 @@ 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++;
@@ -7575,36 +7612,80 @@ 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.
- }
- if (&Apache::loncommon::connection_aborted($r)) { last; }
- }
+ 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.
+ }
+
$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('
');
+ 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(' '.&Apache::loncommon::start_data_table()."\n".
+ &Apache::loncommon::start_data_table_header_row()."\n".
+ '