'.
''.
- ' No | '.
- ' '.($ENV{'form.vProb'} eq 'no' ? 'Title' : 'Problem View').'/Grade | ';
+ ' Prob. | '.
+ ' '.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade | ';
- my ($depth,$ctr,$question) = (1,0,1);
+ &Apache::lonxml::clear_problem_counter();
+ my ($depth,$question,$prob) = (1,1,1);
$iterator->next(); # skip the first BEGIN_MAP
my $curRes = $iterator->next(); # for "current resource"
- while ($depth > 0 && $ctr < 100) { # ctr, just in case it never gets out of loop
+ while ($depth > 0) {
if($curRes == $iterator->BEGIN_MAP) { $depth++; }
- if($curRes == $iterator->END_MAP) { $depth++; }
+ if($curRes == $iterator->END_MAP) { $depth--; }
- if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) {
+ if (ref($curRes) && $curRes->is_problem()) {
my $parts = $curRes->parts();
- $parts = &temp_parts_fix($parts); # remove line when lonnavmap is fixed
my $title = $curRes->compTitle();
my $symbx = $curRes->symb();
- $studentTable.=''.$question.
- (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' | ';
+ $studentTable.=' '.$prob.
+ (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' | ';
$studentTable.='';
- if ($ENV{'form.vProb'} eq 'yes') {
- $studentTable.=&show_problem($request,$symbx,$uname,$udom,1);
+ my %form = ('CODE' => $env{'form.CODE'},);
+ if ($env{'form.vProb'} eq 'yes' ) {
+ $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
+ undef,'both',\%form);
} else {
- my $companswer = &Apache::loncommon::get_student_answers(
- $symbx,$uname,$udom,$ENV{'request.course.id'});
+ my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
$companswer =~ s|||g;
-
# while ($companswer =~ /()/s) { #\n");
# }
-# $companswer =~ s///g;
- $studentTable.=' '.$title.' Correct answer: '.$companswer;
+# $companswer =~ s|||g;
+ $studentTable.=' '.$title.' Correct answer: '.$companswer;
}
- my %record = &Apache::lonnet::restore($symbx,$ENV{'request.course.id'},$udom,$uname);
+ my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
- if ($ENV{'form.lastSub'} eq 'datesub') {
+ if ($env{'form.lastSub'} eq 'datesub') {
if ($record{'version'} eq '') {
- $studentTable.=' No recorded submission for this problem ';
+ $studentTable.=' No recorded submission for this problem ';
} else {
- $studentTable.=''.
- ''.
- 'Date/Time | '.
- 'Submission | '.
- 'Status | ';
- my ($version);
- for ($version=1;$version<=$record{'version'};$version++) {
- my $timestamp = scalar(localtime($record{$version.':timestamp'}));
- $studentTable.=''.$timestamp.' | ';
- my @versionKeys = split(/\:/,$record{$version.':keys'});
- my @displaySub = ();
- foreach my $partid (@{$parts}) {
- my @matchKey = grep /^resource\.$partid\..*?\.submission$/,@versionKeys;
- next if ($record{"$version:resource.$partid.solved"} eq '');
-# next if ($record{"$version:resource.$partid.award"} eq 'APPROX_ANS' &&
-# $record{"$version:resource.$partid.solved"} eq '');
- $displaySub[0].=(exists $record{$version.':'.$matchKey[0]}) ?
- 'Part '.$partid.' '.
- ($record{"$version:resource.$partid.tries"} eq '' ? 'Trial not counted' :
- 'Trial '.$record{"$version:resource.$partid.tries"}).' '.
- $record{$version.':'.$matchKey[0]}.' ' : '';
- $displaySub[1].=(exists $record{"$version:resource.$partid.award"}) ?
- 'Part '.$partid.' '.
- $record{"$version:resource.$partid.award"}.'/'.
- $record{"$version:resource.$partid.solved"}.' ' : '';
- $displaySub[2].=(exists $record{"$version:resource.$partid.regrader"}) ?
- $record{"$version:resource.$partid.regrader"}.' (Part: '.$partid.')' : '';
+ my %responseType = ();
+ foreach my $partid (@{$parts}) {
+ my @responseIds =$curRes->responseIds($partid);
+ my @responseType =$curRes->responseType($partid);
+ my %responseIds;
+ for (my $i=0;$i<=$#responseIds;$i++) {
+ $responseIds{$responseIds[$i]}=$responseType[$i];
}
- $displaySub[2].=(exists $record{"$version:resource.regrader"}) ?
- $record{"$version:resource.regrader"} : '';
- $studentTable.=''.$displaySub[0].' | '.$displaySub[1].
- ($displaySub[2] eq '' ? '' : 'Manually graded by '.$displaySub[2]).' | ';
+ $responseType{$partid} = \%responseIds;
}
- $studentTable.='
| ';
+ $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
+
}
- } elsif ($ENV{'form.lastSub'} eq 'all') {
- my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : '');
+ } elsif ($env{'form.lastSub'} eq 'all') {
+ my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
- $ENV{'request.course.id'},
+ $env{'request.course.id'},
'','.submission');
}
-
- foreach my $partid (@{$parts}) {
- $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
- $studentTable.=''."\n";
- $question++;
+ if (&canmodify($usec)) {
+ foreach my $partid (@{$parts}) {
+ $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
+ $studentTable.=''."\n";
+ $question++;
+ }
+ $prob++;
}
$studentTable.='';
- }
+ }
$curRes = $iterator->next();
- $ctr++;
}
- $studentTable.=' '."\n".
- ' '.
+ $studentTable.=' '."\n".
+ ''.
''."\n";
- $studentTable.=&show_grading_menu_form($symb,$url);
+ $studentTable.=&show_grading_menu_form($symb);
$request->print($studentTable);
return '';
}
-sub temp_parts_fix { #remove sub once lonnavmap is fixed
- my $parts = shift;
- my %seen = ();
- my @correctParts = ();
- foreach (@{$parts}) {
- next if ($seen{$_} > 0);
- $seen{$_}++;
- push @correctParts,$_;
+sub displaySubByDates {
+ my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
+ my $isCODE=0;
+ my $isTask = ($symb =~/\.task$/);
+ if (exists($record->{'resource.CODE'})) { $isCODE=1; }
+ my $studentTable=&Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ ' | '.&mt('Date/Time').' | '.
+ ($isCODE?''.&mt('CODE').' | ':'').
+ ''.&mt('Submission').' | '.
+ ''.&mt('Status').' | '.
+ &Apache::loncommon::end_data_table_header_row();
+ my ($version);
+ my %mark;
+ my %orders;
+ $mark{'correct_by_student'} = $checkIcon;
+ if (!exists($$record{'1:timestamp'})) {
+ return ' '.&mt('Nothing submitted - no attempts').' ';
+ }
+
+ my $interaction;
+ for ($version=1;$version<=$$record{'version'};$version++) {
+ my $timestamp =
+ &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
+ if (exists($$record{$version.':resource.0.version'})) {
+ $interaction = $$record{$version.':resource.0.version'};
+ }
+
+ my $where = ($isTask ? "$version:resource.$interaction"
+ : "$version:resource");
+ $studentTable.=&Apache::loncommon::start_data_table_row().
+ ''.$timestamp.' | ';
+ if ($isCODE) {
+ $studentTable.=''.$record->{$version.':resource.CODE'}.' | ';
+ }
+ my @versionKeys = split(/\:/,$$record{$version.':keys'});
+ my @displaySub = ();
+ foreach my $partid (@{$parts}) {
+ my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
+ : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
+
+
+# next if ($$record{"$version:resource.$partid.solved"} eq '');
+ my $display_part=&get_display_part($partid,$symb);
+ foreach my $matchKey (@matchKey) {
+ if (exists($$record{$version.':'.$matchKey}) &&
+ $$record{$version.':'.$matchKey} ne '') {
+
+ my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
+ : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
+ $displaySub[0].=''.&mt('Part:').' '.$display_part.' ';
+ $displaySub[0].='('.&mt('ID').' '.
+ $responseId.') ';
+ if ($$record{"$where.$partid.tries"} eq '') {
+ $displaySub[0].=&mt('Trial not counted');
+ } else {
+ $displaySub[0].=&mt('Trial [_1]',
+ $$record{"$where.$partid.tries"});
+ }
+ my $responseType=($isTask ? 'Task'
+ : $responseType->{$partid}->{$responseId});
+ if (!exists($orders{$partid})) { $orders{$partid}={}; }
+ if (!exists($orders{$partid}->{$responseId})) {
+ $orders{$partid}->{$responseId}=
+ &get_order($partid,$responseId,$symb,$uname,$udom);
+ }
+ $displaySub[0].=' '.
+ &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).' ';
+ }
+ }
+ if (exists($$record{"$where.$partid.checkedin"})) {
+ $displaySub[1].='Checked in by '.
+ $$record{"$where.$partid.checkedin"}.' into slot '.
+ $$record{"$where.$partid.checkedin.slot"}.
+ ' ';
+ }
+ if (exists $$record{"$where.$partid.award"}) {
+ $displaySub[1].='Part: '.$display_part.' '.
+ lc($$record{"$where.$partid.award"}).' '.
+ $mark{$$record{"$where.$partid.solved"}}.
+ ' ';
+ }
+ if (exists $$record{"$where.$partid.regrader"}) {
+ $displaySub[2].=$$record{"$where.$partid.regrader"}.
+ ' ('.&mt('Part').': '.$display_part.')';
+ } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
+ $displaySub[2].=
+ $$record{"$version:resource.$partid.regrader"}.
+ ' ('.&mt('Part').': '.$display_part.')';
+ }
+ }
+ # needed because old essay regrader has not parts info
+ if (exists $$record{"$version:resource.regrader"}) {
+ $displaySub[2].=$$record{"$version:resource.regrader"};
+ }
+ $studentTable.=''.$displaySub[0].' | '.$displaySub[1];
+ if ($displaySub[2]) {
+ $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
+ }
+ $studentTable.=' | '.
+ &Apache::loncommon::end_data_table_row();
}
- return \@correctParts;
+ $studentTable.=&Apache::loncommon::end_data_table();
+ return $studentTable;
}
sub updateGradeByPage {
my ($request) = shift;
- my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"};
- my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"};
- my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};
- my $pageTitle = $ENV{'form.page'};
- my (undef,undef,$fullname) = &getclasslist($getsec,'1');
- my ($uname,$udom) = split(/:/,$ENV{'form.student'});
-
- my $result=' '.$ENV{'form.title'}.'';
- $result.=' Student: '.$$fullname{$ENV{'form.student'}}.
- ' ('.$uname.($udom eq $cdom ? '':':'.$udom).')'."\n";
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
+ my $pageTitle = $env{'form.page'};
+ my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
+ my ($uname,$udom) = split(/:/,$env{'form.student'});
+ my $usec=$classlist->{$env{'form.student'}}[5];
+ if (!&canmodify($usec)) {
+ $request->print('Unable to modify requested student.('.$env{'form.student'}.'');
+ $request->print(&show_grading_menu_form($env{'form.symb'}));
+ return;
+ }
+ my $result=' '.$env{'form.title'}.'';
+ $result.=' Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
+ ''."\n";
$request->print($result);
- my $navmap = Apache::lonnavmaps::navmap-> new($ENV{'request.course.fn'}.'.db',
- $ENV{'request.course.fn'}.'_parms.db',1, 1);
- my ($mapUrl, $id, $resUrl) = split(/___/, $ENV{'form.page'});
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
-
+ if (!$map) {
+ $request->print('Unable to grade requested sequence. ('.$resUrl.')');
+ my ($symb)=&get_symb($request);
+ $request->print(&show_grading_menu_form($symb));
+ return;
+ }
my $iterator = $navmap->getIterator($map->map_start(),
$map->map_finish());
my $studentTable=''.
''.
- ' No | '.
+ ' Prob. | '.
' Title | '.
' Previous Score | '.
' New Score | ';
$iterator->next(); # skip the first BEGIN_MAP
my $curRes = $iterator->next(); # for "current resource"
- my ($depth,$ctr,$question,$changeflag)= (1,0,1,0);
- while ($depth > 0 && $ctr < 100) { # ctr, just in case it never gets out of loop
+ my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
+ while ($depth > 0) {
if($curRes == $iterator->BEGIN_MAP) { $depth++; }
- if($curRes == $iterator->END_MAP) { $depth++; }
+ if($curRes == $iterator->END_MAP) { $depth--; }
- if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) {
+ if (ref($curRes) && $curRes->is_problem()) {
my $parts = $curRes->parts();
- $parts = &temp_parts_fix($parts); # remove line when lonnavmap is fixed
my $title = $curRes->compTitle();
my $symbx = $curRes->symb();
- $studentTable.=''.$question.
- (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' | ';
+ $studentTable.=' '.$prob.
+ (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' | ';
$studentTable.=' '.$title.' | ';
my %newrecord=();
my @displayPts=();
+ my %aggregate = ();
+ my $aggregateflag = 0;
foreach my $partid (@{$parts}) {
- my $newpts = $ENV{'form.GD_BOX'.$question.'_'.$partid};
- my $oldpts = $ENV{'form.oldpts'.$question.'_'.$partid};
+ my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
+ my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
- my $wgt = $ENV{'form.WGT'.$question.'_'.$partid} != 0 ?
- $ENV{'form.WGT'.$question.'_'.$partid} : 1;
+ my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ?
+ $env{'form.WGT'.$question.'_'.$partid} : 1;
my $partial = $newpts/$wgt;
my $score;
if ($partial > 0) {
$score = 'correct_by_override';
- } elsif ($partial == 0) {
+ } elsif ($newpts ne '') { #empty is taken as 0
$score = 'incorrect_by_override';
}
- if ($ENV{'form.GD_SEL'.$question.'_'.$partid} eq 'excused') {
+ my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
+ if ($dropMenu eq 'excused') {
$partial = '';
$score = 'excused';
+ } elsif ($dropMenu eq 'reset status'
+ && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
+ $newrecord{'resource.'.$partid.'.tries'} = 0;
+ $newrecord{'resource.'.$partid.'.solved'} = '';
+ $newrecord{'resource.'.$partid.'.award'} = '';
+ $newrecord{'resource.'.$partid.'.awarded'} = 0;
+ $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
+ $changeflag++;
+ $newpts = '';
+
+ my $aggtries = $env{'form.aggtries'.$question.'_'.$partid};
+ my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
+ my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
+ if ($aggtries > 0) {
+ &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
+ $aggregateflag = 1;
+ }
}
- my $oldstatus = $ENV{'form.solved'.$question.'_'.$partid};
- $displayPts[0].=' Part '.$partid.' = '.
+ my $display_part=&get_display_part($partid,$curRes->symb());
+ my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
+ $displayPts[0].=' Part: '.$display_part.' = '.
(($oldstatus eq 'excused') ? 'excused' : $oldpts).
- ' ';
- $displayPts[1].=' Part '.$partid.' = '.
- ($oldstatus eq 'correct_by_student' ? $oldpts :
- (($score eq 'excused') ? 'excused' : $newpts)).
- ' ';
-
+ ' ';
+ $displayPts[1].=' Part: '.$display_part.' = '.
+ (($score eq 'excused') ? 'excused' : $newpts).
+ ' ';
$question++;
- if (($oldstatus eq 'correct_by_student') ||
- ($newpts eq $oldpts && $score eq $oldstatus))
- {
- next;
- }
+ next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
+
$newrecord{'resource.'.$partid.'.awarded'} = $partial if $partial ne '';
- $newrecord{'resource.'.$partid.'.solved'} = $score;
- $newrecord{'resource.'.$partid.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";
+ $newrecord{'resource.'.$partid.'.solved'} = $score if $score ne '';
+ $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
+ if (scalar(keys(%newrecord)) > 0);
$changeflag++;
}
if (scalar(keys(%newrecord)) > 0) {
- &Apache::lonnet::cstore(\%newrecord,$symbx,$ENV{'request.course.id'},
+ my %record =
+ &Apache::lonnet::restore($symbx,$env{'request.course.id'},
+ $udom,$uname);
+
+ if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
+ $newrecord{'resource.CODE'} = $env{'form.CODE'};
+ } elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
+ $newrecord{'resource.CODE'} = '';
+ }
+ &Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
$udom,$uname);
+ %record = &Apache::lonnet::restore($symbx,
+ $env{'request.course.id'},
+ $udom,$uname);
+ &check_and_remove_from_queue($parts,\%record,undef,$symbx,
+ $cdom,$cnum,$udom,$uname);
}
+
+ if ($aggregateflag) {
+ &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
+ $env{'course.'.$env{'request.course.id'}.'.domain'},
+ $env{'course.'.$env{'request.course.id'}.'.num'});
+ }
+
$studentTable.=''.$displayPts[0].' | '.
''.$displayPts[1].' | '.
' ';
+ $prob++;
}
$curRes = $iterator->next();
- $ctr++;
}
$studentTable.='
| ';
- $studentTable.=&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'});
+ $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.'));
@@ -2776,25 +4580,94 @@ sub updateGradeByPage {
#
#------ start of section for handling grading by page/sequence ---------
+=pod
+
+=head1 Bubble sheet grading routines
+
+ For this documentation:
+
+ 'scanline' refers to the full line of characters
+ from the file that we are parsing that represents one entire sheet
+
+ 'bubble line' refers to the data
+ representing the line of bubbles that are on the physical bubble sheet
+
+
+The overall process is that a scanned in bubble sheet data is uploaded
+into a course. When a user wants to grade, they select a
+sequence/folder of resources, a file of bubble sheet info, and pick
+one of the predefined configurations for what each scanline looks
+like.
+
+Next each scanline is checked for any errors of either 'missing
+bubbles' (it's an error because it may have been mis-scanned
+because too light bubbling), 'double bubble' (each bubble line should
+have no more that one letter picked), invalid or duplicated CODE,
+invalid student ID
+
+If the CODE option is used that determines the randomization of the
+homework problems, either way the student ID is looked up into a
+username:domain.
+
+During the validation phase the instructor can choose to skip scanlines.
+
+After the validation phase, there are now 3 bubble sheet files
+
+ scantron_original_filename (unmodified original file)
+ scantron_corrected_filename (file where the corrected information has replaced the original information)
+ scantron_skipped_filename (contains the exact text of scanlines that where skipped)
+
+Also there is a separate hash nohist_scantrondata that contains extra
+correction information that isn't representable in the bubble sheet
+file (see &scantron_getfile() for more information)
+
+After all scanlines are either valid, marked as valid or skipped, then
+foreach line foreach problem in the picked sequence, an ssi request is
+made that simulates a user submitting their selected letter(s) against
+the homework problem.
+
+=over 4
+
+
+
+=item defaultFormData
+
+ Returns html hidden inputs used to hold context/default values.
+
+ Arguments:
+ $symb - $symb of the current resource
+
+=cut
+
sub defaultFormData {
- my ($symb,$url)=@_;
- return '
- '."\n".
- ''."\n".
- ''."\n".
- ''."\n";
+ my ($symb)=@_;
+ return ''."\n".
+ ''."\n".
+ ''."\n";
}
+
+=pod
+
+=item getSequenceDropDown
+
+ Return html dropdown of possible sequences to grade
+
+ Arguments:
+ $symb - $symb of the current resource
+
+=cut
+
sub getSequenceDropDown {
- my ($request,$symb)=@_;
+ my ($symb)=@_;
my $result='
---|
|