--- loncom/homework/grades.pm 2013/06/29 16:27:39 1.596.2.12.2.17 +++ loncom/homework/grades.pm 2011/10/09 16:23:34 1.656 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.596.2.12.2.17 2013/06/29 16:27:39 raeburn Exp $ +# $Id: grades.pm,v 1.656 2011/10/09 16:23:34 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -43,7 +43,8 @@ use Apache::lonmsg(); use Apache::Constants qw(:common :http); use Apache::lonlocal; use Apache::lonenc; -use Apache::bridgetask(); +use Apache::lonstathelpers; +use Apache::lonquickgrades; use String::Similarity; use LONCAPA; @@ -52,7 +53,6 @@ use POSIX qw(floor); my %perm=(); -my %old_essays=(); # These variables are used to recover from ssi errors @@ -98,6 +98,9 @@ sub ssi_print_error { # # --- Retrieve the parts from the metadata file.--- +# Returns an array of everything that the resources stores away +# + sub getpartlist { my ($symb,$errorref) = @_; @@ -122,24 +125,6 @@ sub getpartlist { return @stores; } -# --- Get the symbolic name of a problem and the url -sub get_symb { - my ($request,$silent) = @_; - my $symb=$env{'form.symb'}; - unless ($symb) { - (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--; - $symb = &Apache::lonnet::symbread($url); - if ($symb eq '') { - if (!$silent) { - $request->print(&mt("Unable to handle ambiguous references: [_1].",$url)); - return (); - } - } - } - &Apache::lonenc::check_decrypt(\$symb); - return ($symb); -} - #--- Format fullname, username:domain if different for display #--- Use anywhere where the student names are listed sub nameUserString { @@ -154,6 +139,7 @@ sub nameUserString { #--- Get the partlist and the response type for a given problem. --- #--- Indicate if a response type is coded handgraded or not. --- +#--- Sets response_error pointer to "1" if navmaps object broken --- sub response_type { my ($symb,$response_error) = @_; @@ -212,59 +198,9 @@ sub get_display_part { return $display; } -#--- Show resource title -#--- and parts and response type -sub showResourceInfo { - my ($symb,$probTitle,$checkboxes,$res_error) = @_; - my $result = '
'; + $grayFont.$bottomrow.''.''; } elsif ($response eq 'match') { my %answer=&Apache::lonnet::str2hash($answer); my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"}); @@ -426,7 +362,7 @@ sub cleanRecord { ''. '
'. ' '.&mt('Answer').' '.$toprow.''.$grayFont.&mt('Option ID').' '. - $bottomrow.'
'; + $bottomrow.''.''; } elsif ($response eq 'essay') { if (! exists ($env{'form.'.$symb})) { my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade', @@ -509,8 +445,7 @@ sub cleanRecord { #-- A couple of common js functions sub commonJSfunctions { my $request = shift; - $request->print(<'. '
'. ' '.&mt('Answer').' '.$toprow.''.$grayFont.&mt('Option ID').' '. - $bottomrow.'
'.&mt('No grading privileges').'
'); return; } else { $request->print(''."\n"); } # essay grading message center - if ($env{'form.handgrade'} eq 'yes') { +# if ($env{'form.handgrade'} eq 'yes') { + if (1) { my $result=''.&mt('[_1]Message:[_2] No more students for this section or class.','','').'
'."\n"; - $the_end.=&mt('Click on the button below to return to the grading menu.').''.&mt('[_1]Message:[_2] No more students for this section or class.','','').'
'."\n"; $request->print($the_end); } return ''; @@ -3193,8 +2895,8 @@ sub handback_files { my $part_resp = join('_',@{ $part_response_id }); if (($env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'} =~ /^\d+$/) & ($new_part eq $part_id)) { for (my $counter=1; $counter<=$env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'}; $counter++) { - # if multiple files are uploaded names will be 'returndoc2','returndoc3' - if ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter}) { + # if multiple files are uploaded names will be 'returndoc2','returndoc3' + if ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter}) { my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter.'.filename'}; my ($directory,$answer_file) = ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter} =~ /^(.*?)([^\/]*)$/); @@ -3202,11 +2904,9 @@ sub handback_files { &file_name_version_ext($answer_file); my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/); my $getpropath = 1; - my ($dir_list,$listerror) = - &Apache::lonnet::dirlist($portfolio_root.$portfolio_path, - $domain,$stuname,$getpropath); - my $version = &get_next_version($answer_name,$answer_ext,$dir_list); - # fix filename + my @dir_list = &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,$domain,$stuname,$getpropath); + my $version = &get_next_version($answer_name, $answer_ext, \@dir_list); + # fix file name my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/); my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain, $newflg.'_'.$part_resp.'_returndoc'.$counter, @@ -3223,10 +2923,9 @@ sub handback_files { $$newrecord{"resource.$new_part.$resp_id.handback"}.=','; } $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name; - $file_msg.=''.$save_file_name."'.
&mt('Number of records updated = [_1] for [quant,_2,student].',
$rec_update,$count).'
'.
@@ -4190,21 +3878,14 @@ sub csvuploadmap_header {
$javascript=&csvupload_javascript_forward_associate();
}
- my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
- my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
- my $ignore=&mt('Ignore First Line');
$symb = &Apache::lonenc::check_encrypt($symb);
+ $request->print('
". &mt("Failed to save data for student [_1]. Message when trying to save was: [_2]", "$username:$domain",$result)."
"); } $request->rflush(); - $countdone++; } } $request->print('
@@ -6718,11 +6219,10 @@ sub scantron_warning_screen {
'.&mt("If this information is correct, please click on '[_1]'.",&mt($button_text)).' '.&mt("If something is incorrect, please click the 'Grading Menu' button to start over.").' '.&mt('If this information is correct, please click on \'[_1]\'.',&mt($button_text)).' '.&mt("You have not selected the format of the student's response data.").'
-'.&mt('Sequence to be Graded:').' '.$title.'
-'.$CODElist.$lastbubblepoints.'
+'.$CODElist.'
'.&mt('Data File that will be used:').' '.$env{'form.scantron_selectfile'}.'
-
+'.&mt('If something is incorrect, please return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','','').'
');
@@ -6738,8 +6238,7 @@ sub scantron_warning_screen {
=cut
sub scantron_do_warning {
- my ($r)=@_;
- my ($symb)=&get_symb($r);
+ my ($r,$symb)=@_;
if (!$symb) {return '';}
my $default_form_data=&defaultFormData($symb);
$r->print(&scantron_form_start().$default_form_data);
@@ -6757,15 +6256,14 @@ sub scantron_do_warning {
$r->print('
".&show_grading_menu_form($symb));
+ $r->print("
");
return '';
}
@@ -6802,11 +6300,9 @@ SCANTRONFORM
''."\n";
$chunk .=
''."\n";
- $chunk .=
- ''."\n";
$result .= $chunk;
$line++;
- }
+ }
return $result;
}
@@ -6814,7 +6310,7 @@ SCANTRONFORM
=item scantron_validate_file
- Dispatch routine for doing validation of a bubblesheet data file.
+ Dispatch routine for doing validation of a bubble sheet data file.
Also processes any necessary information resets that need to
occur before validation begins (ignore previous corrections,
@@ -6823,8 +6319,7 @@ SCANTRONFORM
=cut
sub scantron_validate_file {
- my ($r) = @_;
- my ($symb)=&get_symb($r);
+ my ($r,$symb) = @_;
if (!$symb) {return '';}
my $default_form_data=&defaultFormData($symb);
@@ -6859,9 +6354,6 @@ sub scantron_validate_file {
return '';
}
my $result=&scantron_form_start($max_bubble).$default_form_data;
- if ($env{'form.scantron_lastbubblepoints'} ne '') {
- $result .= '';
- }
$r->print($result);
my @validate_phases=( 'sequence',
@@ -6879,7 +6371,6 @@ sub scantron_validate_file {
while (!$stop && $currentphase < scalar(@validate_phases)) {
$r->print(&mt('Validating '.$validate_phases[$currentphase]).'
');
$r->rflush();
-
my $which="scantron_validate_".$validate_phases[$currentphase];
{
no strict 'refs';
@@ -6887,7 +6378,7 @@ sub scantron_validate_file {
}
}
if (!$stop) {
- my $warning=&scantron_warning_screen('Start Grading');
+ my $warning=&scantron_warning_screen('Start Grading',$symb);
$r->print(&mt('Validation process complete.').'
'.
$warning.
&mt('Perform verification for each student after storage of submissions?').
@@ -6897,7 +6388,7 @@ sub scantron_validate_file {
''.&mt('No').
'
'.
&mt('Grading will take longer if you use verification.').'
'.
- &mt("Alternatively, the 'Review bubblesheet data' utility (see grading menu) can be used for all students after grading is complete.").'
'.
+ &mt('Otherwise, Grade/Manage/Review Bubblesheets [_1] Review bubblesheet data can be used once grading is complete.','»').'
'.
''.
''."\n");
} else {
@@ -6909,7 +6400,7 @@ sub scantron_validate_file {
$r->print('');
$r->print(' '.&mt('this error').'
');
- $r->print("
".&mt("Or click the 'Grading Menu' button to start over.")."
"); + $r->print(''.&mt('Or return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','','').'
'); } else { if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') { $r->print(''); @@ -6921,7 +6412,7 @@ sub scantron_validate_file { $r->print(" ".&mt("this scanline saving it for later.")); } } - $r->print("' - .&mt('Some resources in the sequence currently are not set to' - .' exam mode. Grading these resources currently may not' - .' work correctly.') - .'
' - ); + $r->print("".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."
"); return (1,$currentphase); } } @@ -7374,35 +6860,25 @@ sub scantron_validate_ID { sub scantron_get_correction { - my ($r,$i,$scan_record,$scan_config,$line,$error,$arg, - $randomorder,$randompick,$respnumlookup,$startline)=@_; + my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_; #FIXME in the case of a duplicated ID the previous line, probably need #to show both the current line and the previous one and allow skipping #the previous one or the current one if ( $$scan_record{'scantron.PaperID'} =~ /\S/) { - $r->print( - '' - .&mt('An error was detected ([_1]) for PaperID [_2]', - "$error", - ''.$$scan_record{'scantron.PaperID'}.'') - ."
\n"); - } else { - $r->print( - '' - .&mt('An error was detected ([_1]) in scanline [_2] [_3]', - "$error", $i, "
$line") - ." \n"); - } - my $message = - '
'
- .&mt('The ID on the form is [_1]',
- "$$scan_record{'scantron.ID'}")
- .'
'
- .&mt('The name on the paper is [_1], [_2]',
- $$scan_record{'scantron.LastName'},
- $$scan_record{'scantron.FirstName'})
- .'
".&mt("An error was detected ($error)". + " for PaperID [_1]", + $$scan_record{'scantron.PaperID'})."
\n"); + } else { + $r->print("".&mt("An error was detected ($error)". + " in scanline [_1]
[_2]", + $i,$line)." \n"); + } + my $message="
".&mt("The ID on the form is [_1]
".
+ "The name on the paper is [_2],[_3]",
+ $$scan_record{'scantron.ID'},
+ $$scan_record{'scantron.LastName'},
+ $$scan_record{'scantron.FirstName'})."
'.&mt("The encoded ID is not in the classlist"). + $r->print("
".&mt("The encoded ID is not in the classlist"). "
\n"); } elsif ($error eq 'duplicateID') { - $r->print(''.&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."
\n"); + $r->print("".&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."
\n"); } $r->print($message); $r->print("".&mt("How should I handle this?")."
\n");
@@ -7425,21 +6901,20 @@ sub scantron_get_correction {
$r->print(&Apache::loncommon::selectstudent_link('scantronupload',
'scantron_username','scantron_domain'));
$r->print(": ");
- $r->print("\n:\n".
+ $r->print("\n@".
&Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
$r->print('');
} elsif ($error =~ /CODE$/) {
if ($error eq 'incorrectCODE') {
- $r->print('
'.&mt("The encoded CODE is not in the list of possible CODEs.")."
\n"); + $r->print("".&mt("The encoded CODE is not in the list of possible CODEs.")."
\n"); } elsif ($error eq 'duplicateCODE') { - $r->print(''.&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."
\n"); + $r->print("".&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."
\n"); } - $r->print("".&mt('The CODE on the form is [_1]', - "'$$scan_record{'scantron.CODE'}'") - ."
\n"); + $r->print("".&mt("The CODE on the form is '[_1]'",
+ $$scan_record{'scantron.CODE'})."
\n");
$r->print($message);
- $r->print("
".&mt("How should I handle this?")."
\n"); + $r->print("".&mt("How should I handle this?")." '.&mt("There have been multiple bubbles scanned for some question(s)")." ".&mt("There have been multiple bubbles scanned for some question(s)")." ".&mt("Please indicate which bubble should be used for grading")." '.&mt("There have been [_1]no[_2] bubbles scanned for some question(s)",'','')." ".&mt("There have been no bubbles scanned for some question(s)")." ".&mt("Please indicate which bubble should be used for grading.")." '.
- &mt('If you have already graded these by bubbling sheets to indicate points awarded, [_1]what point value is assigned to a filled last bubble in each row?',' ');
+ $r->print(' ');
if ($scancode eq '') {
- $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2].',
+ $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 bubblesheet for user: [_1] with ID: [_2] and CODE: [_3].',
+ $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('
\n");
$r->print("\n
");
my $i=0;
if ($error eq 'incorrectCODE'
@@ -7466,14 +6941,13 @@ sub scantron_get_correction {
$r->print("
");
$r->print("\n
");
}
- $r->print(<
");
} elsif ($error eq 'doublebubble') {
- $r->print('
');
- if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
- ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
- ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
- ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
- ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
- ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
- $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the bubblesheet exam directed students to leave [quant,_1,line] blank on their bubblesheets.",$lines).'
'.&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.').'
'.&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.').'
'.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'
');
+ if (($responsetype_per_response{$question-1} eq 'essayresponse') ||
+ ($responsetype_per_response{$question-1} eq 'formularesponse') ||
+ ($responsetype_per_response{$question-1} eq 'stringresponse') ||
+ ($responsetype_per_response{$question-1} eq 'imageresponse') ||
+ ($responsetype_per_response{$question-1} eq 'reactionresponse') ||
+ ($responsetype_per_response{$question-1} eq 'organicresponse')) {
+ $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the exam directed students to leave [quant,_1,line] blank on their bubblesheets.",$lines).'
'.&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.').'
'.&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.').'
'.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'
');
} else {
$r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."
");
}
}
for (my $i =0; $i < $lines; $i++) {
my $selected = $$scan_record{"scantron.$current_line.answer"};
- &scantron_bubble_selector($r,$scan_config,$current_line,
+ &scantron_bubble_selector($r,$scan_config,$current_line,
$questionnum,$error,split('', $selected));
push(@linenums,$current_line);
$current_line++;
@@ -7787,7 +7200,7 @@ sub scantron_bubble_selector {
my $max=$$scan_config{'Qlength'};
my $scmode=$$scan_config{'Qon'};
- if ($scmode eq 'number' || $scmode eq 'letter') {
+ if ($scmode eq 'number' || $scmode eq 'letter') {
if (($$scan_config{'BubblesPerRow'} =~ /^\d+$/) &&
($$scan_config{'BubblesPerRow'} > 0)) {
$max=$$scan_config{'BubblesPerRow'};
@@ -8008,42 +7421,11 @@ sub scantron_validate_doublebubble {
#get student info
my $classlist=&Apache::loncoursedata::get_classlist();
my %idmap=&username_to_idmap($classlist);
- my (undef,undef,$sequence)=
- &Apache::lonnet::decode_symb($env{'form.selectpage'});
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
-
- my $navmap = Apache::lonnavmaps::navmap->new();
- unless (ref($navmap)) {
- $r->print(&navmap_errormsg());
- return(1,$currentphase);
- }
- my $map=$navmap->getResourceByUrl($sequence);
- my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
- my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
- %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
- my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
-
my $nav_error;
- if (ref($map)) {
- $randomorder = $map->randomorder();
- $randompick = $map->randompick();
- if ($randomorder || $randompick) {
- $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
- if ($nav_error) {
- $r->print(&navmap_errormsg());
- return(1,$currentphase);
- }
- &graders_resources_pass(\@resources,\%grader_partids_by_symb,
- \%grader_randomlists_by_symb,$bubbles_per_row);
- }
- } else {
- $r->print(&navmap_errormsg());
- return(1,$currentphase);
- }
-
&scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble line array.
if ($nav_error) {
$r->print(&navmap_errormsg());
@@ -8054,15 +7436,11 @@ sub scantron_validate_doublebubble {
my $line=&scantron_get_line($scanlines,$scan_data,$i);
if ($line=~/^[\s\cz]*$/) { next; }
my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
- $scan_data,undef,\%idmap,$randomorder,
- $randompick,$sequence,\@master_seq,
- \%symb_to_resource,\%grader_partids_by_symb,
- \%orderedforcode,\%respnumlookup,\%startline);
+ $scan_data);
if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
&scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
'doublebubble',
- $$scan_record{'scantron.doubleerror'},
- $randomorder,$randompick,\%respnumlookup,\%startline);
+ $$scan_record{'scantron.doubleerror'});
return (1,$currentphase);
}
return (0,$currentphase+1);
@@ -8101,14 +7479,11 @@ sub scantron_get_maxbubble {
%first_bubble_line = ();
%subdivided_bubble_lines = ();
%responsetype_per_response = ();
- %masterseq_id_responsenum = ();
my $response_number = 0;
my $bubble_line = 0;
foreach my $resource (@resources) {
- my $resid = $resource->id();
- my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,
- $udom,undef,$bubbles_per_row);
+ my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom,undef,$bubbles_per_row);
if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
foreach my $part_id (@{$parts}) {
my $lines;
@@ -8157,7 +7532,6 @@ sub scantron_get_maxbubble {
$bubble_lines_per_response{$response_number} = $lines;
$responsetype_per_response{$response_number} =
$analysis->{$part_id.'.type'};
- $masterseq_id_responsenum{$resid.'_'.$part_id} = $response_number;
$response_number++;
$bubble_line += $lines;
@@ -8190,59 +7564,21 @@ sub scantron_validate_missingbubbles {
#get student info
my $classlist=&Apache::loncoursedata::get_classlist();
my %idmap=&username_to_idmap($classlist);
- my (undef,undef,$sequence)=
- &Apache::lonnet::decode_symb($env{'form.selectpage'});
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
-
- my $navmap = Apache::lonnavmaps::navmap->new();
- unless (ref($navmap)) {
- $r->print(&navmap_errormsg());
- return(1,$currentphase);
- }
-
- my $map=$navmap->getResourceByUrl($sequence);
- my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
- my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
- %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
- my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
-
my $nav_error;
- if (ref($map)) {
- $randomorder = $map->randomorder();
- $randompick = $map->randompick();
- if ($randomorder || $randompick) {
- $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
- if ($nav_error) {
- $r->print(&navmap_errormsg());
- return(1,$currentphase);
- }
- &graders_resources_pass(\@resources,\%grader_partids_by_symb,
- \%grader_randomlists_by_symb,$bubbles_per_row);
- }
- } else {
- $r->print(&navmap_errormsg());
- return(1,$currentphase);
- }
-
-
my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
if ($nav_error) {
- $r->print(&navmap_errormsg());
return(1,$currentphase);
}
-
if (!$max_bubble) { $max_bubble=2**31; }
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
my $line=&scantron_get_line($scanlines,$scan_data,$i);
if ($line=~/^[\s\cz]*$/) { next; }
- my $scan_record =
- &scantron_parse_scanline($line,$i,\%scantron_config,$scan_data,undef,\%idmap,
- $randomorder,$randompick,$sequence,\@master_seq,
- \%symb_to_resource,\%grader_partids_by_symb,
- \%orderedforcode,\%respnumlookup,\%startline);
+ my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
+ $scan_data);
if (!defined($$scan_record{'scantron.missingerror'})) { next; }
my @to_correct;
@@ -8251,45 +7587,28 @@ sub scantron_validate_missingbubbles {
foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
my $lastbubble;
if ($missing =~ /^(\d+)\.(\d+)$/) {
- my $question = $1;
- my $subquestion = $2;
- my ($first,$responsenum);
- if ($randomorder || $randompick) {
- $responsenum = $respnumlookup{$question-1};
- $first = $startline{$question-1};
- } else {
- $responsenum = $question-1;
- $first = $first_bubble_line{$responsenum};
- }
- if (!defined($first)) { next; }
- my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
- my $subcount = 1;
- while ($subcount<$subquestion) {
- $first += $subans[$subcount-1];
- $subcount ++;
- }
- my $count = $subans[$subquestion-1];
- $lastbubble = $first + $count;
+ my $question = $1;
+ my $subquestion = $2;
+ if (!defined($first_bubble_line{$question -1})) { next; }
+ my $first = $first_bubble_line{$question-1};
+ my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+ my $subcount = 1;
+ while ($subcount<$subquestion) {
+ $first += $subans[$subcount-1];
+ $subcount ++;
+ }
+ my $count = $subans[$subquestion-1];
+ $lastbubble = $first + $count;
} else {
- my ($first,$responsenum);
- if ($randomorder || $randompick) {
- $responsenum = $respnumlookup{$missing-1};
- $first = $startline{$missing-1};
- } else {
- $responsenum = $missing-1;
- $first = $first_bubble_line{$responsenum};
- }
- if (!defined($first)) { next; }
- $lastbubble = $first + $bubble_lines_per_response{$responsenum};
+ if (!defined($first_bubble_line{$missing - 1})) { next; }
+ $lastbubble = $first_bubble_line{$missing - 1} + $bubble_lines_per_response{$missing - 1};
}
if ($lastbubble > $max_bubble) { next; }
push(@to_correct,$missing);
}
if (@to_correct) {
&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
- $line,'missingbubble',\@to_correct,
- $randomorder,$randompick,\%respnumlookup,
- \%startline);
+ $line,'missingbubble',\@to_correct);
return (1,$currentphase);
}
@@ -8297,54 +7616,19 @@ sub scantron_validate_missingbubbles {
return (0,$currentphase+1);
}
-sub hand_bubble_option {
- my (undef, undef, $sequence) =
- &Apache::lonnet::decode_symb($env{'form.selectpage'});
- return if ($sequence eq '');
- my $navmap = Apache::lonnavmaps::navmap->new();
- unless (ref($navmap)) {
- return;
- }
- my $needs_hand_bubbles;
- my $map=$navmap->getResourceByUrl($sequence);
- my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
- foreach my $res (@resources) {
- if (ref($res)) {
- if ($res->is_problem()) {
- my $partlist = $res->parts();
- foreach my $part (@{ $partlist }) {
- my @types = $res->responseType($part);
- if (grep(/^(chem|essay|image|formula|math|string|functionplot)$/,@types)) {
- $needs_hand_bubbles = 1;
- last;
- }
- }
- }
- }
- }
- if ($needs_hand_bubbles) {
- my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
- my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
- return &mt('The sequence to be graded contains response types which are handgraded.').'
').
- ' '.&mt('or').' '.
- '
');
@@ -8397,6 +7690,7 @@ SCANTRONFORM
my $i=-1;
my $started;
+ my $nav_error;
&scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
if ($nav_error) {
$r->print(&navmap_errormsg());
@@ -8409,14 +7703,12 @@ SCANTRONFORM
if ($ssi_error) {
$r->print("");
&ssi_print_error($r);
- $r->print(&show_grading_menu_form($symb));
&Apache::lonnet::remove_lock($lock);
return ''; # Dunno why the other returns return '' rather than just returning.
}
my %lettdig = &letter_to_digits();
my $numletts = scalar(keys(%lettdig));
- my %orderedforcode;
while ($i<$scanlines->{'count'}) {
($uname,$udom)=('','');
@@ -8428,15 +7720,8 @@ SCANTRONFORM
'last student');
}
$started=1;
- my %respnumlookup = ();
- my %startline = ();
- my $total;
my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
- $scan_data,undef,\%idmap,$randomorder,
- $randompick,$sequence,\@master_seq,
- \%symb_to_resource,\%grader_partids_by_symb,
- \%orderedforcode,\%respnumlookup,\%startline,
- \$total);
+ $scan_data);
unless ($uname=&scantron_find_student($scan_record,$scan_data,
\%idmap,$i)) {
&scantron_add_delay(\@delayqueue,$line,
@@ -8448,26 +7733,10 @@ SCANTRONFORM
'Student '.$uname.' has multiple sheets',2);
next;
}
- my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
- my $user = $uname.':'.$usec;
($uname,$udom)=split(/:/,$uname);
- my $scancode;
- if ((exists($scan_record->{'scantron.CODE'})) &&
- (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
- $scancode = $scan_record->{'scantron.CODE'};
- } else {
- $scancode = '';
- }
-
- my @mapresources = @resources;
- if ($randomorder || $randompick) {
- @mapresources =
- &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
- \%orderedforcode);
- }
my (%partids_by_symb,$res_error);
- foreach my $resource (@mapresources) {
+ foreach my $resource (@resources) {
my $ressymb;
if (ref($resource)) {
$ressymb = $resource->symb();
@@ -8478,8 +7747,7 @@ SCANTRONFORM
if ((exists($grader_randomlists_by_symb{$ressymb})) ||
(ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
my ($analysis,$parts) =
- &scantron_partids_tograde($resource,$env{'request.course.id'},
- $uname,$udom,undef,$bubbles_per_row);
+ &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom,undef,$bubbles_per_row);
$partids_by_symb{$ressymb} = $parts;
} else {
$partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
@@ -8499,83 +7767,69 @@ SCANTRONFORM
&scantron_putfile($scanlines,$scan_data);
}
+ 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,
- \@mapresources,\%partids_by_symb,
- $bubbles_per_row,$randomorder,$randompick,
- \%respnumlookup,\%startline)
- eq 'ssi_error') {
+ \@resources,\%partids_by_symb,
+ $bubbles_per_row) 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 (($scancode) && ($randomorder || $randompick)) {
- my $parmresult =
- &Apache::lonparmset::storeparm_by_symb($symb,
- '0_examcode',2,$scancode,
- 'string_examcode',$uname,
- $udom);
- }
$completedstudents{$uname}={'line'=>$line};
if ($env{'form.verifyrecord'}) {
my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
- if ($randompick) {
- if ($total) {
- $lastpos = $total*$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 (@mapresources) {
+ foreach my $resource (@resources) {
my $ressymb = $resource->symb();
($counter,my $recording) =
&verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
$counter,$studentdata,$partids_by_symb{$ressymb},
- \%scantron_config,\%lettdig,$numletts,$randomorder,
- $randompick,\%respnumlookup,\%startline);
+ \%scantron_config,\%lettdig,$numletts);
$studentrecord .= $recording;
}
if ($studentrecord ne $studentdata) {
&Apache::lonxml::clear_problem_counter();
if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
- \@mapresources,\%partids_by_symb,
- $bubbles_per_row,$randomorder,$randompick,
- \%respnumlookup,\%startline)
- eq 'ssi_error') {
+ \@resources,\%partids_by_symb,
+ $bubbles_per_row) 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);
delete($completedstudents{$uname});
return '';
}
$counter = -1;
$studentrecord = '';
- foreach my $resource (@mapresources) {
+ foreach my $resource (@resources) {
my $ressymb = $resource->symb();
($counter,my $recording) =
&verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
$counter,$studentdata,$partids_by_symb{$ressymb},
- \%scantron_config,\%lettdig,$numletts,
- $randomorder,$randompick,\%respnumlookup,
- \%startline);
+ \%scantron_config,\%lettdig,$numletts);
$studentrecord .= $recording;
}
if ($studentrecord ne $studentdata) {
- $r->print('
'.&Apache::loncommon::start_data_table()."\n".
@@ -8583,11 +7837,11 @@ SCANTRONFORM
''.&mt('Source').' '.&mt('Bubbled responses').' '.
&Apache::loncommon::end_data_table_header_row()."\n".
&Apache::loncommon::start_data_table_row().
- ''.&mt('Bubblesheet').' '.
+ ''.&mt('Bubble Sheet').' '.
''.$studentdata.' '.
&Apache::loncommon::end_data_table_row().
&Apache::loncommon::start_data_table_row().
- ''.&mt('Stored submissions').' '.
+ 'Stored submissions '.
''.$studentrecord.' '."\n".
&Apache::loncommon::end_data_table_row().
&Apache::loncommon::end_data_table().'
took $lasttime
"); $r->print(""); - $r->print(&show_grading_menu_form($symb)); return ''; } @@ -8623,8 +7876,7 @@ sub graders_resources_pass { my $ressymb = $resource->symb(); my ($analysis,$parts) = &scantron_partids_tograde($resource,$env{'request.course.id'}, - $env{'user.name'},$env{'user.domain'}, - 1,$bubbles_per_row); + $env{'user.name'},$env{'user.domain'},1,$bubbles_per_row); $grader_partids_by_symb->{$ressymb} = $parts; if (ref($analysis) eq 'HASH') { if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') { @@ -8637,67 +7889,9 @@ sub graders_resources_pass { return; } -=pod - -=item users_order - - Returns array of resources in current map, ordered based on either CODE, - if this is a CODEd exam, or based on student's identity if this is a - "NAMEd" exam. - - Should be used when randomorder and/or randompick applied when the - corresponding exam was printed, prior to students completing bubblesheets - for the version of the exam the student received. - -=cut - -sub users_order { - my ($user,$scancode,$mapurl,$master_seq,$symb_to_resource,$orderedforcode) = @_; - my @mapresources; - unless ((ref($master_seq) eq 'ARRAY') && (ref($symb_to_resource) eq 'HASH')) { - return @mapresources; - } - if ($scancode) { - if ((ref($orderedforcode) eq 'HASH') && (ref($orderedforcode->{$scancode}) eq 'ARRAY')) { - @mapresources = @{$orderedforcode->{$scancode}}; - } else { - $env{'form.CODE'} = $scancode; - my $actual_seq = - &Apache::lonprintout::master_seq_to_person_seq($mapurl, - $master_seq, - $user,$scancode,1); - if (ref($actual_seq) eq 'ARRAY') { - @mapresources = map { $symb_to_resource->{$_}; } @{$actual_seq}; - if (ref($orderedforcode) eq 'HASH') { - if (@mapresources > 0) { - $orderedforcode->{$scancode} = \@mapresources; - } - } - } - delete($env{'form.CODE'}); - } - } else { - my $actual_seq = - &Apache::lonprintout::master_seq_to_person_seq($mapurl, - $master_seq, - $user,undef,1); - if (ref($actual_seq) eq 'ARRAY') { - @mapresources = - map { $symb_to_resource->{$_}; } @{$actual_seq}; - } - } - return @mapresources; -} - sub grade_student_bubbles { - my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row, - $randomorder,$randompick,$respnumlookup,$startline) = @_; - my $uselookup = 0; - if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH') && - (ref($startline) eq 'HASH')) { - $uselookup = 1; - } - + my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row) = @_; +# Walk folder as student here to get resources in order student sees. if (ref($resources) eq 'ARRAY') { my $count = 0; foreach my $resource (@{$resources}) { @@ -8713,18 +7907,11 @@ sub grade_student_bubbles { if ($bubbles_per_row ne '') { $form{'bubbles_per_row'} = $bubbles_per_row; } - if ($env{'form.scantron_lastbubblepoints'} ne '') { - $form{'scantron_lastbubblepoints'} = $env{'form.scantron_lastbubblepoints'}; - } if (ref($parts) eq 'HASH') { if (ref($parts->{$ressymb}) eq 'ARRAY') { foreach my $part (@{$parts->{$ressymb}}) { - if ($uselookup) { - $form{'scantron_questnum_start.'.$part} = $startline->{$count} + 1; - } else { - $form{'scantron_questnum_start.'.$part} = - 1+$env{'form.scantron.first_bubble_line.'.$count}; - } + $form{'scantron_questnum_start.'.$part} = + 1+$env{'form.scantron.first_bubble_line.'.$count}; $count++; } } @@ -8738,7 +7925,7 @@ sub grade_student_bubbles { } sub scantron_upload_scantron_data { - my ($r)=@_; + my ($r,$symb)=@_; my $dom = $env{'request.role.domain'}; my $domdesc = &Apache::lonnet::domain($dom,'description'); $r->print(&Apache::loncommon::coursebrowser_javascript($dom)); @@ -8746,13 +7933,11 @@ sub scantron_upload_scantron_data { 'domainid', 'coursename',$dom); my $syllabuslink = ''.&mt('Syllabus').''. - (' 'x2).&mt('(shows course personnel)'); - my ($symb) = &get_symb($r,1); + (' 'x2).&mt('(shows course personnel)'); my $default_form_data=&defaultFormData($symb); my $nofile_alert = &mt('Please use the browse button to select a file from your local directory.'); my $nocourseid_alert = &mt("Please use the 'Select Course' link to open a separate window where you can search for a course to which a file can be uploaded."); - $r->print(' - - +')); + $r->print('- '.&mt('The requested filename was invalid.').' + '.&mt('The requested file name was invalid.').'
'); - $r->print(&show_grading_menu_form($symb)); return; } my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file; @@ -8987,15 +8167,12 @@ sub scantron_download_scantron_data { '','').' '); - $r->print(&show_grading_menu_form($symb)); return ''; } sub checkscantron_results { - my ($r) = @_; - my ($symb)=&get_symb($r); + my ($r,$symb) = @_; if (!$symb) {return '';} - my $grading_menu_button=&show_grading_menu_form($symb); my $cid = $env{'request.course.id'}; my %lettdig = &letter_to_digits(); my $numletts = scalar(keys(%lettdig)); @@ -9015,20 +8192,10 @@ sub checkscantron_results { return ''; } my $map=$navmap->getResourceByUrl($sequence); - my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb, - %grader_randomlists_by_symb,%orderedforcode); - if (ref($map)) { - $randomorder=$map->randomorder(); - $randompick=$map->randompick(); - } my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); - my $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource); - if ($nav_error) { - $r->print(&navmap_errormsg()); - return ''; - } - &graders_resources_pass(\@resources,\%grader_partids_by_symb, - \%grader_randomlists_by_symb,$bubbles_per_row); + my (%grader_partids_by_symb,%grader_randomlists_by_symb); + &graders_resources_pass(\@resources,\%grader_partids_by_symb, \%grader_randomlists_by_symb); + my ($uname,$udom); my (%scandata,%lastname,%bylast); $r->print(' @@ -9037,9 +8204,12 @@ sub checkscantron_results { my @delayqueue; my %completedstudents; - my $count=&get_todo_count($scanlines,$scan_data); - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count); - my ($username,$domain,$started,%ordered); + my $count=&Apache::grades::get_todo_count($scanlines,$scan_data); + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Bubblesheet/Submissions Comparison Status', + 'Progress of Bubblesheet Data/Submission Records Comparison',$count, + 'inline',undef,'checkscantron'); + my ($username,$domain,$started); + my $nav_error; &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse. if ($nav_error) { $r->print(&navmap_errormsg()); @@ -9064,8 +8234,8 @@ sub checkscantron_results { my $scan_record= &Apache::grades::scantron_parse_scanline($line,$i,\%scantron_config, $scan_data); - unless ($uname=&scantron_find_student($scan_record,$scan_data, - \%idmap,$i)) { + unless ($uname=&Apache::grades::scantron_find_student($scan_record,$scan_data, + \%idmap,$i)) { &Apache::grades::scantron_add_delay(\@delayqueue,$line, 'Unable to find a student that matches',1); next; @@ -9078,57 +8248,26 @@ sub checkscantron_results { my $pid = $scan_record->{'scantron.ID'}; $lastname{$pid} = $scan_record->{'scantron.LastName'}; push(@{$bylast{$lastname{$pid}}},$pid); - my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION]; - my $user = $uname.':'.$usec; - ($username,$domain)=split(/:/,$uname); - - my $scancode; - if ((exists($scan_record->{'scantron.CODE'})) && - (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) { - $scancode = $scan_record->{'scantron.CODE'}; - } else { - $scancode = ''; - } - - my @mapresources = @resources; my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'}; - my %respnumlookup=(); - my %startline=(); - if ($randomorder || $randompick) { - @mapresources = - &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource, - \%orderedforcode); - my $total = &get_respnum_lookups($sequence,$scan_data,\%idmap,$line, - $scan_record,\@master_seq,\%symb_to_resource, - \%grader_partids_by_symb,\%orderedforcode, - \%respnumlookup,\%startline); - if ($randompick && $total) { - $lastpos = $total*$scantron_config{'Qlength'}; - } - } $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos); chomp($scandata{$pid}); $scandata{$pid} =~ s/\r$//; - + ($username,$domain)=split(/:/,$uname); my $counter = -1; - foreach my $resource (@mapresources) { + foreach my $resource (@resources) { my $parts; my $ressymb = $resource->symb(); if ((exists($grader_randomlists_by_symb{$ressymb})) || (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) { (my $analysis,$parts) = - &scantron_partids_tograde($resource,$env{'request.course.id'}, - $username,$domain,undef, - $bubbles_per_row); + &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain,undef,$bubbles_per_row); } else { $parts = $grader_partids_by_symb{$ressymb}; } ($counter,my $recording) = &verify_scantron_grading($resource,$domain,$username,$cid,$counter, $scandata{$pid},$parts, - \%scantron_config,\%lettdig,$numletts, - $randomorder,$randompick, - \%respnumlookup,\%startline); + \%scantron_config,\%lettdig,$numletts); $record{$pid} .= $recording; } } @@ -9166,19 +8305,16 @@ sub checkscantron_results { } } } - $r->print(''. - &mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for [_1][quant,_2,student][_3] ([quant,_4,bubblesheet line] per student).', - '', - $numstudents, - '', - $env{'form.scantron_maxbubble'}). - '
' + $r->print( + '' + .&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for [_1][quant,_2,student][_3] ([quant,_4,bubblesheet line] per student).', + '', + $numstudents, + '', + $env{'form.scantron_maxbubble'}) + .'
' ); - $r->print(''
- .&mt('Exact matches for [_1][quant,_2,student][_3].','',$passed,'')
- .'
'
- .&mt('Discrepancies detected for [_1][quant,_2,student][_3].','',$failed,'')
- .'
'.&mt('Exact matches for [quant,_1,student].',$passed).'
'.&mt('Discrepancies detected for [quant,_1,student].',$failed).'
'."\n";
- $result.='
|
-
|
'.&mt('Access Denied ([_1])',$command).'
'); } } if ($ssi_error) { &ssi_print_error($request); } + &Apache::lonquickgrades::endGradeScreen($request); $request->print(&Apache::loncommon::end_page()); &reset_caches(); return OK; @@ -10560,16 +9591,6 @@ ssi_with_retries() - missingbubble - array ref of the bubble lines that have missing bubble errors - $randomorder - True if exam folder has randomorder set - $randompick - True if exam folder has randompick set - $respnumlookup - Reference to HASH mapping question numbers in bubble lines - for current line to question number used for same question - in "Master Seqence" (as seen by Course Coordinator). - $startline - Reference to hash where key is question number (0 is first) - and value is number of first bubble line for current student - or code-based randompick and/or randomorder. - - =item scantron_get_maxbubble() : Arguments: @@ -10590,7 +9611,7 @@ ssi_with_retries() $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 + 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. @@ -10604,7 +9625,7 @@ ssi_with_retries() =item scantron_process_students() : - Routine that does the actual grading of the bubblesheet information. + Routine that does the actual grading of the bubble sheet information. The parsed scanline hash is added to %env @@ -10624,7 +9645,7 @@ ssi_with_retries() =item scantron_upload_scantron_data() : - Creates the screen for adding a new bubblesheet data file to a course. + Creates the screen for adding a new bubble sheet data file to a course. =item scantron_upload_scantron_data_save() : @@ -10638,7 +9659,7 @@ ssi_with_retries() =item scantron_download_scantron_data() : Shows a list of the three internal files (original, corrected, - skipped) for a specific bubblesheet data file that exists in the + skipped) for a specific bubble sheet data file that exists in the course. =item scantron_validate_ID() :