--- loncom/homework/grades.pm 2007/07/25 00:00:23 1.424 +++ loncom/homework/grades.pm 2007/09/02 02:10:31 1.438 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.424 2007/07/25 00:00:23 albertel Exp $ +# $Id: grades.pm,v 1.438 2007/09/02 02:10:31 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -45,8 +45,11 @@ use LONCAPA; use POSIX qw(floor); -my %oldessays=(); + my %perm=(); +my %bubble_lines_per_response; # no. bubble lines for each response. + # index is "symb.part_id" + # ----- These first few routines are general use routines.---- # @@ -195,22 +198,54 @@ sub showResourceInfo { return $result,$responseType,$hdgrade,$partlist,$handgrade; } +sub reset_caches { + &reset_analyze_cache(); + &reset_perm(); +} + +{ + my %analyze_cache; -sub get_order { - my ($partid,$respid,$symb,$uname,$udom)=@_; - my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); - $url=&Apache::lonnet::clutter($url); - my $subresult=&Apache::lonnet::ssi($url, - ('grade_target' => 'analyze'), - ('grade_domain' => $udom), - ('grade_symb' => $symb), - ('grade_courseid' => - $env{'request.course.id'}), - ('grade_username' => $uname)); - (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); - my %analyze=&Apache::lonnet::str2hash($subresult); - return ($analyze{"$partid.$respid.shown"}); + sub reset_analyze_cache { + undef(%analyze_cache); + } + + sub get_analyze { + my ($symb,$uname,$udom)=@_; + my $key = "$symb\0$uname\0$udom"; + return $analyze_cache{$key} if (exists($analyze_cache{$key})); + + my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); + $url=&Apache::lonnet::clutter($url); + my $subresult=&Apache::lonnet::ssi($url, + ('grade_target' => 'analyze'), + ('grade_domain' => $udom), + ('grade_symb' => $symb), + ('grade_courseid' => + $env{'request.course.id'}), + ('grade_username' => $uname)); + (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); + my %analyze=&Apache::lonnet::str2hash($subresult); + return $analyze_cache{$key} = \%analyze; + } + + sub get_order { + my ($partid,$respid,$symb,$uname,$udom)=@_; + my $analyze = &get_analyze($symb,$uname,$udom); + return $analyze->{"$partid.$respid.shown"}; + } + + sub get_radiobutton_correct_foil { + my ($partid,$respid,$symb,$uname,$udom)=@_; + my $analyze = &get_analyze($symb,$uname,$udom); + foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) { + if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') { + return $foil; + } + } + } } + #--- Clean response type for display #--- Currently filters option/rank/radiobutton/match/essay/Task # response types only. @@ -259,11 +294,11 @@ sub cleanRecord { } elsif ($response eq 'radiobutton') { my %answer=&Apache::lonnet::str2hash($answer); my ($toprow,$bottomrow); - my $correct=($order->[0])+1; - for (my $i=1;$i<=$#$order;$i++) { - my $foil=$order->[$i]; + my $correct = + &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom); + foreach my $foil (@$order) { if (exists($answer{$foil})) { - if ($i == $correct) { + if ($foil eq $correct) { $toprow.='<td><b>true</b></td>'; } else { $toprow.='<td><i>true</i></td>'; @@ -538,7 +573,7 @@ sub compute_points { # sub most_similar { - my ($uname,$udom,$uessay)=@_; + my ($uname,$udom,$uessay,$old_essays)=@_; # ignore spaces and punctuation @@ -555,23 +590,22 @@ sub most_similar { my $scrsid=''; my $sessay=''; # go through all essays ... - foreach my $tkey (keys %oldessays) { - my ($tname,$tdom,$tcrsid)=split(/\./,$tkey); + foreach my $tkey (keys(%$old_essays)) { + my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey)); # ... except the same student - if (($tname ne $uname) || ($tdom ne $udom)) { - my $tessay=$oldessays{$tkey}; - $tessay=~s/\W+/ /gs; + next if (($tname eq $uname) && ($tdom eq $udom)); + my $tessay=$old_essays->{$tkey}; + $tessay=~s/\W+/ /gs; # String similarity gives up if not even limit - my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); + my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); # Found one - if ($tsimilar>$limit) { - $limit=$tsimilar; - $sname=$tname; - $sdom=$tdom; - $scrsid=$tcrsid; - $sessay=$oldessays{$tkey}; - } - } + if ($tsimilar>$limit) { + $limit=$tsimilar; + $sname=$tname; + $sdom=$tdom; + $scrsid=$tcrsid; + $sessay=$old_essays->{$tkey}; + } } if ($limit>0.6) { return ($sname,$sdom,$scrsid,$sessay,$limit); @@ -725,7 +759,6 @@ LISTJAVASCRIPT my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'}; $env{'form.Status'} = $saveStatus; - $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n". '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n". '<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n". @@ -736,8 +769,7 @@ LISTJAVASCRIPT '<option value=".25">Quarter Points</option>'. '<option value=".1">Tenths of a Point</option>'. '</select>'. - - '<input type="hidden" name="section" value="'.$getsec.'" />'."\n". + &build_section_inputs(). '<input type="hidden" name="submitonly" value="'.$submitonly.'" />'."\n". '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" /><br />'."\n". '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n". @@ -1663,6 +1695,19 @@ sub download_all_link { return } +sub build_section_inputs { + my $section_inputs; + if ($env{'form.section'} eq '') { + $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n"; + } else { + my @sections = &Apache::loncommon::get_env_multiple('form.section'); + foreach my $section (@sections) { + $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n"; + } + } + return $section_inputs; +} + # --------------------------- show submissions of a student, option to grade sub submission { my ($request,$counter,$total) = @_; @@ -1691,6 +1736,7 @@ sub submission { '" src="'.$request->dir_config('lonIconsURL'). '/check.gif" height="16" border="0" />'; + my %old_essays; # header info if ($counter == 0) { &sub_page_js($request); @@ -1742,7 +1788,6 @@ sub submission { $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0'; } my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'}; - $request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n". '<input type="hidden" name="command" value="handgrade" />'."\n". '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". @@ -1757,7 +1802,7 @@ sub submission { '<input type="hidden" name="vProb" value="'.$env{'form.vProb'}.'" />'."\n". '<input type="hidden" name="vAns" value="'.$env{'form.vAns'}.'" />'."\n". '<input type="hidden" name="lastSub" value="'.$env{'form.lastSub'}.'" />'."\n". - '<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n". + &build_section_inputs(). '<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n". '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" />'."\n". '<input type="hidden" name="NCT"'. @@ -1805,7 +1850,7 @@ KEYWORDS my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/); $apath=&escape($apath); $apath=~s/\W/\_/gs; - %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); + %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); } } @@ -1943,12 +1988,21 @@ KEYWORDS my $similar=''; if($env{'form.checkPlag'}){ my ($oname,$odom,$ocrsid,$oessay,$osim)= - &most_similar($uname,$udom,$subval); + &most_similar($uname,$udom,$subval,\%old_essays); if ($osim) { $osim=int($osim*100.0); - $similar="<hr /><h3><span class=\"LC_warning\">Essay". - " is $osim% similar to an essay by ". - &Apache::loncommon::plainname($oname,$odom). + my %old_course_desc = + &Apache::lonnet::coursedescription($ocrsid, + {'one_time' => 1}); + + $similar="<hr /><h3><span class=\"LC_warning\">". + &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])', + $osim, + &Apache::loncommon::plainname($oname,$odom), + $oname,$odom, + $old_course_desc{'description'}, + $old_course_desc{'num'}, + $old_course_desc{'domain'}). '</span></h3><blockquote><i>'. &keywords_highlight($oessay). '</i></blockquote><hr />'; @@ -2957,20 +3011,21 @@ sub viewgrades { $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n". '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="command" value="editgrades" />'."\n". - '<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n". + &build_section_inputs(). '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n"; my $sectionClass; + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); if ($env{'form.section'} eq 'all') { $sectionClass='Class </h3>'; } elsif ($env{'form.section'} eq 'none') { - $sectionClass='Students in no Section </h3>'; + $sectionClass=&mt('Students in no Section').'</h3>'; } else { - $sectionClass='Students in Section '.$env{'form.section'}.'</h3>'; + $sectionClass=&mt('Students in Section(s) [_1]',$section_display).'</h3>'; } - $result.='<h3>Assign Common Grade To '.$sectionClass; + $result.='<h3>'.&mt('Assign Common Grade To [_1]',$sectionClass); $result.= '<table border=0><tr><td bgcolor="#777777">'."\n". '<table border=0><tr bgcolor="#ffffdd"><td>'; #radio buttons/text box for assigning points for a section or class. @@ -3076,8 +3131,11 @@ sub viewgrades { 'onClick="javascript:submit();" target="_self" /></form>'."\n"; if (scalar(%$fullname) eq 0) { my $colspan=3+scalar(@parts); - $result='<span class="LC_warning">There are no students in section "'.$env{'form.section'}. - '" with enrollment status "'.$env{'form.Status'}.'" to modify or grade.</span>'; + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); + $result='<span class="LC_warning">'. + &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade', + $section_display, $env{'form.Status'}). + '</span>'; } $result.=&show_grading_menu_form($symb); return $result; @@ -3154,9 +3212,10 @@ sub editgrades { my ($request) = @_; my $symb=&get_symb($request); - my $title='<h3><span class="LC_info">Current Grade Status</span></h3>'; - $title.='<h4><b>Current Resource: </b>'.$env{'form.probTitle'}.'</h4><br />'."\n"; - $title.='<h4><b>Section: </b>'.$env{'form.section'}.'</h4>'."\n"; + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); + my $title='<h3><span class="LC_info">'.&mt('Current Grade Status').'</span></h3>'; + $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4><br />'."\n"; + $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n"; my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n"; $result.= '<table border="0"><tr bgcolor="#deffff">'. @@ -3857,9 +3916,9 @@ LISTJAVASCRIPT '<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n". '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> by dates and submissions</label>'."\n". '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n"; - - $result.='<input type="hidden" name="section" value="'.$getsec.'" />'."\n". - '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". + + $result.=&build_section_inputs(); + $result.='<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". '<input type="hidden" name="command" value="displayPage" />'."\n". '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n"; @@ -4373,7 +4432,7 @@ one of the predefined configurations for like. Next each scanline is checked for any errors of either 'missing -bubbles' (it's an error because it may have been missed scanned +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 @@ -4384,7 +4443,7 @@ username:domain. During the validation phase the instructor can choose to skip scanlines. -After the validation phase, there is now 3 bubble sheet files +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) @@ -5666,6 +5725,26 @@ sub scantron_remove_scan_data { count - number of scanlines - second is the scan_data hash possible keys are + ($number refers to scanline numbered $number and thus the key affects + only that scanline + $bubline refers to the specific bubble line element and the aspects + refers to that specific bubble line element) + + $number.user - username:domain to use + $number.CODE_ignore_dup + - ignore the duplicate CODE error + $number.useCODE + - use the CODE in the scanline as is + $number.no_bubble.$bubline + - it is valid that there is no bubbled in bubble + at $number $bubline + remember_skipping + - a frozen hash containing keys of $number and values + of either + 1 - we are on a 'do skipped records pass' and plan + on processing this line + 2 - we are on a 'do skipped records pass' and this + scanline has been marked to skip yet again =cut @@ -6434,6 +6513,7 @@ sub scantron_validate_doublebubble { =cut sub scantron_get_maxbubble { + if (defined($env{'form.scantron_maxbubble'}) && $env{'form.scantron_maxbubble'}) { return $env{'form.scantron_maxbubble'}; @@ -6448,14 +6528,40 @@ sub scantron_get_maxbubble { &Apache::lonxml::clear_problem_counter(); + my $uname = $env{'form.student'}; + my $udom = $env{'form.userdom'}; + my $cid = $env{'request.course.id'}; + my $total_lines = 0; + %bubble_lines_per_response = (); + foreach my $resource (@resources) { + my $symb = $resource->symb(); my $result=&Apache::lonnet::ssi($resource->src(), - ('symb' => $resource->symb())); + ('symb' => $resource->symb()), + ('grade_target' => 'analyze'), + ('grade_courseid' => $cid), + ('grade_domain' => $udom), + ('grade_username' => $uname)); + my (undef, $an) = + split(/_HASH_REF__/,$result, 2); + + my %analysis = &Apache::lonnet::str2hash($an); + + + + foreach my $part_id (@{$analysis{'parts'}}) { + my $bubble_lines = $analysis{"$part_id.bubble_lines"}[0]; + if (!$bubble_lines) { + $bubble_lines = 1; + } + $bubble_lines_per_response{"$symb.$part_id"} = $bubble_lines; + $total_lines = $total_lines + $bubble_lines; + } + } &Apache::lonnet::delenv('scantron\.'); $env{'form.scantron_maxbubble'} = - &Apache::lonxml::get_problem_counter()-1; - + $total_lines; return $env{'form.scantron_maxbubble'}; } @@ -6904,7 +7010,7 @@ GRADINGMENUJS $result.='<table width="100%" border="0">'; $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n". - ' '.&mt('Select Section').': <select name="section">'."\n"; + ' '.&mt('Select Section').': <select name="section" multiple="multiple" size="3">'."\n"; if (ref($sections)) { foreach (sort (@$sections)) { $result.='<option value="'.$_.'" '. @@ -7012,9 +7118,10 @@ sub gather_clicker_ids { # Set up a couple variables. my $username_idx = &Apache::loncoursedata::CL_SNAME(); my $domain_idx = &Apache::loncoursedata::CL_SDOM(); + my $status_idx = &Apache::loncoursedata::CL_STATUS(); foreach my $student (keys(%$classlist)) { - + if ($classlist->{$student}->[$status_idx] ne 'Active') { next; } my $username = $classlist->{$student}->[$username_idx]; my $domain = $classlist->{$student}->[$domain_idx]; my $clickers = @@ -7275,8 +7382,21 @@ ENDHEADER $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />'; $correct_count++; } elsif ($clicker_ids{$id}) { - $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />'; - $student_count++; + if ($clicker_ids{$id}=~/\,/) { +# More than one user with the same clicker! + $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />"; + $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'. + "<select name='multi".$id."'>"; + foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) { + $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>'; + } + $result.='</select>'; + $unknown_count++; + } else { +# Good: found one and only one user with the right clicker + $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />'; + $student_count++; + } } else { $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />"; $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'. @@ -7296,6 +7416,9 @@ ENDHEADER $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>'; } } + if ($number<1) { + $errormsg.="Found no questions."; + } if ($errormsg) { $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>'; } else { @@ -7424,6 +7547,8 @@ ENDHEADER my $id=$1; if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) { $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id}; + } elsif ($env{'form.multi'.$id}) { + $user=$env{'form.multi'.$id}; } } if ($user) { @@ -7469,7 +7594,7 @@ ENDHEADER sub handler { my $request=$_[0]; - &reset_perm(); + &reset_caches(); if ($env{'browser.mathml'}) { &Apache::loncommon::content_type($request,'text/xml'); } else { @@ -7582,6 +7707,7 @@ sub handler { } } $request->print(&Apache::loncommon::end_page()); + &reset_caches(); return ''; }