--- loncom/homework/grades.pm 2003/07/17 22:08:25 1.118 +++ loncom/homework/grades.pm 2003/09/29 21:31:30 1.130.2.1.2.6 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.118 2003/07/17 22:08:25 ng Exp $ +# $Id: grades.pm,v 1.130.2.1.2.6 2003/09/29 21:31:30 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -33,6 +33,7 @@ # June-August H.K. Ng # Year 2003 # February, March H.K. Ng +# July, H. K. Ng # package Apache::grades; @@ -95,10 +96,23 @@ sub get_fullname { return $fullname; } +#--- Format fullname, username:domain if different for display +#--- Use anywhere where the student names are listed +sub nameUserString { + my ($type,$fullname,$uname,$udom) = @_; + if ($type eq 'header') { + return ' Fullname (Username) '; + } else { + return ' '.$fullname.' ('.$uname. + ($ENV{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')'; + } +} + #--- Get the partlist and the response type for a given problem. --- #--- Indicate if a response type is coded handgraded or not. --- sub response_type { - my ($url) = shift; + my ($url,$symb) = shift; + $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url))) if ($symb eq ''); my $allkeys = &Apache::lonnet::metadata($url,'keys'); my %seen = (); my (@partlist,%handgrade); @@ -107,7 +121,8 @@ sub response_type { my ($responsetype,$part) = split(/_/,$_,2); my ($partid,$respid) = split(/_/,$part); $responsetype =~ s/response$//; # make it compatible w/ navmaps - should move to that!! - $handgrade{$part} = $responsetype.':'.($allkeys =~ /parameter_$part\_handgrade/ ? 'yes' : 'no'); + my ($value) = &Apache::lonnet::EXT('resource.'.$part.'.handgrade',$symb); + $handgrade{$part} = $responsetype.':'.($value eq 'yes' ? 'yes' : 'no'); next if ($seen{$partid} > 0); $seen{$partid}++; push @partlist,$partid; @@ -123,24 +138,25 @@ sub showResourceInfo { my $result ='
Current Resource: '.$probTitle.' | ||
Part '.(split(/_/))[0].' | '. + $result.='||
Part '.$partID.' | '. 'Type: '.$responsetype.' | Handgrade: '.$handgrade.' | '; } $result.='
Answer | '. (join ' | ',@ans).' |
'.$grayFont.'Option ID | '.$grayFont. (join ' | '.$grayFont,@IDs).' |
'.&keywords_highlight($answer).''; } return $answer; } @@ -198,6 +229,7 @@ COMMONJSFUNCTIONS #--- section, ids and fullnames for each user. sub getclasslist { my ($getsec,$filterlist) = @_; + $getsec = $getsec eq '' ? 'all' : $getsec; my $classlist=&Apache::loncoursedata::get_classlist(); # Bail out if we were unable to get the classlist return if (! defined($classlist)); @@ -278,7 +310,7 @@ sub student_gradeStatus { my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname); my %partstatus = (); foreach (@$partlist) { - my ($status,$foo) = split(/_/,$record{"resource.$_.solved"},2); + my ($status,undef) = split(/_/,$record{"resource.$_.solved"},2); $status = 'nothing' if ($status eq ''); $partstatus{$_} = $status; my $subkey = "resource.$_.submitted_by"; @@ -304,6 +336,7 @@ sub jscriptNform { ''."\n". ''."\n". ''."\n". + ''."\n". ''."\n". ''."\n". ''."\n". @@ -431,7 +464,7 @@ sub listStudents { my $result='
'.
+ '
|
'.
'
'.$line.''); + substr($line,$where-1,$length)=$answer; + Apache->request->print('a: '.$line.''); + } + return $line; +} + +sub scan_data { + my ($scan_data,$key,$value,$delete)=@_; + my $filename=$ENV{'form.scantron_selectfile'}; + if (defined($value)) { + $scan_data->{$filename.'_'.$key} = $value; + } + if ($delete) { delete($scan_data->{$filename.'_'.$key}); } + return $scan_data->{$filename.'_'.$key}; +} + sub scantron_parse_scanline { - my ($line,$scantron_config)=@_; + my ($line,$whichline,$scantron_config,$scan_data)=@_; my %record; my $questions=substr($line,$$scantron_config{'Qstart'}-1); my $data=substr($line,0,$$scantron_config{'Qstart'}-1); @@ -3172,6 +3332,15 @@ sub scantron_parse_scanline { } $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1, $$scantron_config{'IDlength'}); + $record{'scantron.PaperID'}= + substr($data,$$scantron_config{'PaperID'}-1, + $$scantron_config{'PaperIDlength'}); + $record{'scantron.FirstName'}= + substr($data,$$scantron_config{'FirstName'}-1, + $$scantron_config{'FirstNamelength'}); + $record{'scantron.LastName'}= + substr($data,$$scantron_config{'LastName'}-1, + $$scantron_config{'LastNamelength'}); my @alphabet=('A'..'Z'); my $questnum=0; while ($questions) { @@ -3179,31 +3348,48 @@ sub scantron_parse_scanline { my $currentquest=substr($questions,0,$$scantron_config{'Qlength'}); substr($questions,0,$$scantron_config{'Qlength'})=''; if (length($currentquest) < $$scantron_config{'Qlength'}) { next; } - my (@array)=split(/$$scantron_config{'Qon'}/,$currentquest); - if (scalar(@array) gt 2) { - #FIXME do something intelligent with double bubbles - Apache->request->print(" Wha!!! ".scalar(@array). - '-'.$currentquest.'-'.$questnum.' '); - } + my @array=split($$scantron_config{'Qon'},$currentquest,-1); if (length($array[0]) eq $$scantron_config{'Qlength'}) { $record{"scantron.$questnum.answer"}=''; + if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { + push(@{$record{"scantron.missingerror"}},$questnum); + } } else { $record{"scantron.$questnum.answer"}=$alphabet[length($array[0])]; } + if (scalar(@array) gt 2) { + push(@{$record{'scantron.doubleerror'}},$questnum); + my @ans=@array; + my $i=length($ans[0]);shift(@ans); + while (@ans) { + $i+=length($ans[0])+1; + $record{"scantron.$questnum.answer"}.=$alphabet[$i]; + shift(@ans); + } + } } $record{'scantron.maxquest'}=$questnum; return \%record; } sub scantron_add_delay { + my ($delayqueue,$scanline,$errormessage,$errorcode)=@_; + Apache->request->print('add_delay_error '.$_[2] ); + push(@$delayqueue, + {'line' => $scanline, 'emsg' => $errormessage, + 'ecode' => $errorcode } + ); } sub scantron_find_student { my ($scantron_record,$idmap)=@_; my $scanID=$$scantron_record{'scantron.ID'}; foreach my $id (keys(%$idmap)) { - Apache->request->print(' checking studnet -'.$id.'- againt -'.$scanID.'-'); - if (lc($id) eq lc($scanID)) { Apache->request->print('success');return $$idmap{$id}; } + #Apache->request->print(' checking studnet -'.$id.'- againt -'.$scanID.'-'); + if (lc($id) eq lc($scanID)) { + #Apache->request->print('success'); + return $$idmap{$id}; + } } return undef; } @@ -3216,6 +3402,366 @@ sub scantron_filter { return 0; } +#FIXME I think I am doing this in the wrong order, I think it would be +#better to make a several passes analyzing all of the lines in the +#file for common errors wrong/invalid PID/username duplicated +#PID/username, missing bubbles, double bubbles, missing/invalid CODE +#and then get the instructor to fix all of these errors, then grade +#the corrected one, I'll still need to catch error conditions, but +#maybe most will taken care even before we start + +sub scantron_process_corrections { + my ($r) = @_; + my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); + my ($scanlines,$scan_data)=&scantron_getfile(); + my $classlist=&Apache::loncoursedata::get_classlist(); + my $which=$ENV{'form.scantron_line'}; + my $line=&scantron_get_line($scanlines,$which); + my ($skip,$err,$errmsg); + if ($ENV{'form.scantron_skip_record'}) { + $skip=1; + } elsif ($ENV{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) { + my $newstudent=$ENV{'form.scantron_username'}.':'. + $ENV{'form.scantron_domain'}; + my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID]; + ($line,$err,$errmsg)= + &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which, + 'ID',$newid); + } elsif ($ENV{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) { + foreach my $question (split(',',$ENV{'form.scantron_questions'})) { + ($line,$err,$errmsg)= + &scantron_fixup_scanline(\%scantron_config,$scan_data,$line, + $which,'answer',$question, + $ENV{"form.scantron_correct_Q_$question"}); + if ($err) { last; } + } + } + if ($err) { + $r->print("Unable to accept last correction, an error occurred :$errmsg:"); + } else { + &scantron_put_line($scanlines,$which,$line,$skip); + &scantron_putfile($scanlines,$scan_data); + } +} + + +sub scantron_validate_file { + my ($r) = @_; + my ($symb,$url)=&get_symb_and_url($r); + if (!$symb) {return '';} + my $default_form_data=&defaultFormData($symb,$url); + + if ($ENV{'form.scantron_corrections'}) { + &scantron_process_corrections($r); + } + #get the student pick code ready + $r->print(&Apache::loncommon::studentbrowser_javascript()); + my $result= < Checking ID ".$$scan_record{'scantron.ID'}. + " on paper ID ".$$scan_record{'scantron.PaperID'}." \n"); + my $found; + foreach my $checkid (keys(%idmap)) { + if (lc($checkid) eq lc($id)) { + if ($checkid ne $id) { + $r->print("Using $checkid for encoded $id \n"); + } + $found=$checkid;last; + } + } + if ($found) { + if ($found{'ids'}{$found}) { + #FIXME store away line we prviously saw the ID on + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'duplicateID',$found); + return(1); + } else { + $found{'ids'}{$found}++; + } + } else { + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'incorrectID'); + return(1); + } + } + + return (0,$currentphase+1); +} + +sub scantron_get_correction { + my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_; + +#FIXME in the case of a duplicated ID the previous line, probaly need +#to show both the current line and the previous one and allow skipping +#the previous one or the current one + + $r->print("This scantron record has an error ($error). "); + if ( defined($$scan_record{'scantron.PaperID'}) ) { + $r->print("The current PaperID is ". + $$scan_record{'scantron.PaperID'}." \n"); + } else { + $r->print("The current scanline is ". + $line."\n"); + } + $r->print(''."\n"); + $r->print(''."\n"); + if ($error =~ /ID$/) { + if ($error eq 'unknownID') { + $r->print("The encoded ID is not in the classlist\n"); + } elsif ($error eq 'duplicateID') { + $r->print("The encoded ID has also been used by a previous paper $arg\n"); + } + $r->print(" Original ID is ".$$scan_record{'scantron.ID'}.
+ " Please correct
For question $question, selected bubbles were ".
+ join(" ",split('',$selected)).
+ " Question $question, Please select a bubble to use "); + &scantron_bubble_selector($r,$scan_config,$question); + } + } else { + $r->print("\n
'); +} + +sub scantron_validate_CODE { + my ($r,$currentphase) = @_; + #FIXME doesn't do anything yet + return (0,$currentphase+1); +} + +sub scantron_validate_doublebubble { + my ($r,$currentphase) = @_; + #get student info + my $classlist=&Apache::loncoursedata::get_classlist(); + my %idmap=&username_to_idmap($classlist); + + #get scantron line setup + my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); + my ($scanlines,$scan_data)=&scantron_getfile(); + for (my $i=0;$i<=$scanlines->{'count'};$i++) { + my $line=&scantron_get_line($scanlines,$i); + if (!$line) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); + if (!defined($$scan_record{'scantron.doubleerror'})) { next; } + &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line, + 'doublebubble', + $$scan_record{'scantron.doubleerror'}); + return (1,$currentphase); + } + return (0,$currentphase+1); +} + +sub scantron_validate_missingbubbles { + my ($r,$currentphase) = @_; + #get student info + my $classlist=&Apache::loncoursedata::get_classlist(); + my %idmap=&username_to_idmap($classlist); + + #get scantron line setup + my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); + my ($scanlines,$scan_data)=&scantron_getfile(); + my $max_bubble=$ENV{'form.scantron_maxbubble'}; + if (!$max_bubble) { $max_bubble=2**31; } + for (my $i=0;$i<=$scanlines->{'count'};$i++) { + my $line=&scantron_get_line($scanlines,$i); + if (!$line) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); + if (!defined($$scan_record{'scantron.missingerror'})) { next; } + my @to_correct; + foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) { + if ($missing gt $max_bubble) { next; } + push(@to_correct,$missing); + } + if (@to_correct) { + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'missingbubble',\@to_correct); + return (1,$currentphase); + } + + } + return (0,$currentphase+1); +} + +sub scantron_end_validate_form { + my ($r) = @_; + $r->print(''); +} + sub scantron_process_students { my ($r) = @_; my (undef,undef,$sequence)=split(/___/,$ENV{'form.selectpage'}); @@ -3224,14 +3770,13 @@ sub scantron_process_students { my $default_form_data=&defaultFormData($symb,$url); my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); - my $scanlines=Apache::File->new($Apache::lonnet::perlvar{'lonScansDir'}."/$ENV{'form.scantron_selectfile'}"); - my @scanlines=<$scanlines>; + my ($scanlines,$scan_data)=&scantron_getfile(); my $classlist=&Apache::loncoursedata::get_classlist(); my %idmap=&username_to_idmap($classlist); my $navmap=Apache::lonnavmaps::navmap->new($ENV{'request.course.fn'}.'.db',$ENV{'request.course.fn'}.'_parms.db',1, 1); my $map=$navmap->getResourceByUrl($sequence); my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); - $r->print("geto ".scalar(@resources)." "); +# $r->print("geto ".scalar(@resources)." "); my $result= < line is'.$line.''); + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); my ($uname,$udom); - if ($uname=&scantron_find_student($scan_record,\%idmap)) { + unless ($uname=&scantron_find_student($scan_record,\%idmap)) { &scantron_add_delay(\@delayqueue,$line, - 'Unable to find a student that matches'); + 'Unable to find a student that matches',1); + next; + } + if (exists $completedstudents{$uname}) { + &scantron_add_delay(\@delayqueue,$line, + 'Student '.$uname.' has multiple sheets',2); + next; } $r->print(' doing studnet'.$uname.''); ($uname,$udom)=split(/:/,$uname); &Apache::lonnet::delenv('form.counter'); &Apache::lonnet::appenv(%$scan_record); # &Apache::lonhomework::showhash(%ENV); - $Apache::lonxml::debug=1; - &Apache::lonxml::debug("line is $line"); +# $Apache::lonxml::debug=1; +# &Apache::lonxml::debug("line is $line"); my $i=0; foreach my $resource (@resources) { @@ -3274,31 +3829,31 @@ SCANTRONFORM 'grade_domain' =>$udom, 'grade_courseid'=>$ENV{'request.course.id'}, 'grade_symb' =>$resource->symb())); - my %score=&Apache::lonnet::restore($resource->symb(), - $ENV{'request.course.id'}, - $udom,$uname); - foreach my $part ($resource->{PARTS}) { - if ($score{'resource.'.$part.'.solved'} =~ /^correct/) { - $studentcorrect++; - $totalcorrect++; - } else { - $studentincorrect++; - $totalincorrect++; - } - } - $r->print(' '. - $resource->symb().'-'. - $resource->src().'-'.'result is'.$result); - &Apache::lonhomework::showhash(%score); +# my %score=&Apache::lonnet::restore($resource->symb(), +# $ENV{'request.course.id'}, +# $udom,$uname); +# foreach my $part ($resource->{PARTS}) { +# if ($score{'resource.'.$part.'.solved'} =~ /^correct/) { +# $studentcorrect++; +# $totalcorrect++; +# } else { +# $studentincorrect++; +# $totalincorrect++; +# } +# } +# $r->print(' '. +# $resource->symb().'-'. +# $resource->src().'-'.'result is'.$result); +# &Apache::lonhomework::showhash(%score); # if ($i eq 3) {last;} } + $completedstudents{$uname}={'line'=>$line}; + } continue { &Apache::lonnet::delenv('form.counter'); &Apache::lonnet::delenv('scantron\.'); &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, - 'last student Who got a '.$studentcorrect.' correct and '. - $studentincorrect.' incorrect. The class has gotten '. - $totalcorrect.' correct and '.$totalincorrect.' incorrect'); - last; + 'last student'); + #last; #FIXME #get iterator for $sequence #foreach question 'submit' the students answer to the server @@ -3306,7 +3861,11 @@ SCANTRONFORM # generate data to pass back that includes grade recevied #} } - $Apache::lonxml::debug=0; + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + my $lasttime = &Time::HiRes::time()-$start; + $r->print(" took $lasttime "); + + #$Apache::lonxml::debug=0; foreach my $delay (@delayqueue) { #FIXME #print out each delayed student with interface to select how @@ -3332,7 +3891,7 @@ SCANTRONFORM #--- Show a Grading Menu button - Calls the next routine --- sub show_grading_menu_form { my ($symb,$url)=@_; - my $result.=' | '; @@ -3589,6 +4148,8 @@ sub handler { } } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) { $request->print(&scantron_selectphase($request)); + } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) { + $request->print(&scantron_validate_file($request)); } elsif ($command eq 'scantron_process' && $perm{'mgr'}) { $request->print(&scantron_process_students($request)); } elsif ($command) { @@ -3608,6 +4169,7 @@ sub send_header { #remotewindow.close(); #"); $request->print(&Apache::loncommon::bodytag('Grading')); + $request->rflush(); } sub send_footer { |