--- loncom/homework/grades.pm	2006/01/27 01:52:25	1.306
+++ loncom/homework/grades.pm	2006/02/27 02:06:52	1.319
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.306 2006/01/27 01:52:25 banghart Exp $
+# $Id: grades.pm,v 1.319 2006/02/27 02:06:52 bowersj2 Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -40,6 +40,7 @@ use Apache::lonmsg qw(:user_normal_msg);
 use Apache::Constants qw(:common);
 use Apache::lonlocal;
 use String::Similarity;
+use POSIX qw(floor);
 
 my %oldessays=();
 my %perm=();
@@ -467,6 +468,33 @@ sub jscriptNform {
     return $jscript;
 }
 
+# Given the score (as a number [0-1] and the weight) what is the final
+# point value? This function will round to the nearest tenth, third,
+# or quarter if one of those is within the tolerance of .00001.
+sub compute_points {
+    my ($score, $weight) = @_;
+    
+    my $tolerance = .00001;
+    my $points = $score * $weight;
+
+    # Check for nearness to 1/x.
+    my $check_for_nearness = sub {
+        my ($factor) = @_;
+        my $num = ($points * $factor) + $tolerance;
+        my $floored_num = floor($num);
+        if ($num - $floored_num < 2 * $tolerance * $factor) {
+            return $floored_num / $factor;
+        }
+        return $points;
+    };
+
+    $points = $check_for_nearness->(10);
+    $points = $check_for_nearness->(3);
+    $points = $check_for_nearness->(4);
+    
+    return $points;
+}
+
 #------------------ End of general use routines --------------------
 
 #
@@ -1385,31 +1413,26 @@ SUBJAVASCRIPT
 
 #--- displays the grading box, used in essay type problem and grading by page/sequence
 sub gradeBox {
-    my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
-
+    my ($request,$symb,$uname,$udom,$counter,$partid,$record,$respid) = @_;
     my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL').
 	'/check.gif" height="16" border="0" />';
-
     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
     my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 
 		  '<font color="red">problem weight assigned by computer</font>');
     $wgt       = ($wgt > 0 ? $wgt : '1');
     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
-		  '' : $$record{'resource.'.$partid.'.awarded'}*$wgt);
+		  '' : compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
     my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
-
+    $result.='<br />'.$partid.' - '.$respid.'<br />';
     my $display_part=&get_display_part($partid,undef,$symb);
-
     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
 				       [$partid]);
     my $aggtries = $$record{'resource.'.$partid.'.tries'};
     if ($last_resets{$partid}) {
         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
     }
-
     $result.='<table border="0"><tr><td>'.
 	'<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";
-
     my $ctr = 0;
     $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
     while ($ctr<=$wgt) {
@@ -1421,7 +1444,6 @@ sub gradeBox {
 	$ctr++;
     }
     $result.='</tr></table>';
-
     $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>'."\n";
     $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
 	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
@@ -1430,7 +1452,6 @@ sub gradeBox {
     $result.='<td>/'.$wgt.' '.$wgtmsg.
 	($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
 	' </td><td>'."\n";
-
     $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
 	'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
@@ -1451,8 +1472,24 @@ sub gradeBox {
         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
         $aggtries.'" />'."\n";
     $result.='</td></tr></table>'."\n";
+    $result.=&handbackBox($uname,$udom,$counter,$partid,$record,$respid);
     return $result;
 }
+sub handbackBox {
+    my ($uname,$udom,$counter,$partid,$record,$respid) = @_;
+    my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
+    my $result;
+    if (@$files) {
+        my $file_counter = 0;
+	foreach my $file (@$files) {
+            $result.=' Return commented document to student. <input type="file" name="part'.$partid.'_returndoc';
+            $result.=$file_counter.'" />'."\n";
+            $result.='<input type="hidden" name="respid" value="'.$respid.'" />';
+            $result.='<input type="hidden" name="returndocorig'.$file_counter.'" value="'.$file.'" /><br />';
+        }
+    }
+    return $result;    
+}
 
 sub show_problem {
     my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode) = @_;
@@ -1792,29 +1829,14 @@ KEYWORDS
 			$lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
 			    $display_part.' <font color="#999999">( ID '.$respid.
 			    ' )</font>&nbsp; &nbsp;';
-			my @files;
-			if ($record{"resource.$partid.$respid.portfiles"}) {
-			    my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
-			    foreach my $file (split(',',$record{"resource.$partid.$respid.portfiles"})) {
-				push(@files,$file_url.$file);
-			    
-				&Apache::lonnet::logthis("found a portfolio file".$record{"resource.$partid.$respid.portfiles"});
-				&Apache::lonnet::logthis("uploaded URL file".$record{"resource.$partid.$respid.uploadedurl"});
-			    }
-			}
-			if ($record{"resource.$partid.$respid.uploadedurl"}) {
-			    push(@files,$record{"resource.$partid.$respid.uploadedurl"});
-			}
-			if (@files) {
+			my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
+			if (@$files) {
 			    $lastsubonly.='<br /><font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />';
 			    my $file_counter = 0;
-			    foreach my $file (@files) {
+			    foreach my $file (@$files) {
 			        $file_counter ++;
 				&Apache::lonnet::allowuploaded('/adm/grades',$file);
-				$lastsubonly.='<br /><a href="'.$file.'" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';
-				$lastsubonly.='Return commented document to student. <input type="file" name="returndoc'.$file_counter.'" />'."\n";
-				$lastsubonly.='<input type="hidden" name="returndocorig'.$file_counter.'" value="'.$file.'" />';
-
+				$lastsubonly.='<br /><a href="'.$file.'" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0"> '.$file.'</a>';
 			    }
 			    $lastsubonly.='<br />';
 			}
@@ -1892,15 +1914,18 @@ KEYWORDS
     my %seen = ();
     my @partlist;
     my @gradePartRespid;
-    for (sort keys(%$handgrade)) {
-	my ($partid,$respid) = split(/_/);
-	next if ($seen{$partid} > 0);
+    for my $part_resp(sort keys(%$handgrade)) {
+	my ($partid,$respid) = split(/_/, $part_resp);
+	#next if ($seen{$partid} > 0);
+	if ($seen{$partid} > 0) {
+	    $request->print(&handbackBox($uname,$udom,$counter,$partid,\%record,$respid));
+	    next;
+	}
 	$seen{$partid}++;
-	next if ($$handgrade{$_} =~ /:no$/ && $env{'form.lastSub'} =~ /^(hdgrade)$/);
+	next if ($$handgrade{$part_resp} =~ /:no$/ && $env{'form.lastSub'} =~ /^(hdgrade)$/);
 	push @partlist,$partid;
 	push @gradePartRespid,$partid.'.'.$respid;
-
-	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
+	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record,$respid));
     }
     $result='<input type="hidden" name="partlist'.$counter.
 	'" value="'.(join ":",@partlist).'" />'."\n";
@@ -2029,32 +2054,21 @@ sub processHandGrade {
 		my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
 		foreach my $collabstr (@collabstrs) {
 		    my ($part,@collaborators) = split(/:/,$collabstr);
-		    foreach (@collaborators) {
+		    foreach my $collaborator (@collaborators) {
 			my ($errorflag,$pts,$wgt) = 
-			    &saveHandGrade($request,$url,$symb,$_,$udom,$ctr,
+			    &saveHandGrade($request,$url,$symb,$collaborator,$udom,$ctr,
 					   $env{'form.unamedom'.$ctr},$part);
 			if ($errorflag eq 'not_allowed') {
-			    $request->print("<font color=\"red\">Not allowed to modify grades for $_:$udom</font>");
+			    $request->print("<font color=\"red\">Not allowed to modify grades for $collaborator:$udom</font>");
 			    next;
 			} else {
 			    if ($message ne '') {
-				$msgstatus = &Apache::lonmsg::user_normal_msg($_,$udom,$env{'form.msgsub'},$message);
+				$msgstatus = &Apache::lonmsg::user_normal_msg($collaborator,$udom,$env{'form.msgsub'},$message);
 			    }
-			    
 			}
 		    }
 		}
 	    }
-	    if ($env{'form.returndoc1'}) {
-	        # if multiple files are uploaded names will be 'returndoc2', 'returndoc3'
-	        my $file_counter = 1;
-	        while ($env{'form.returndoc'.$file_counter}) {
-	            my $fname=$env{'form.returndoc'.$file_counter.'.filename'};
-	            $request->print("<br />".$fname." will be the uploaded file name");
-	            $request->print("<font color=\"red\">Will upload document</font>".$env{'form.returndocorig'.$file_counter});
-	            $file_counter ++;
-	        }
-	    }
 	    $ctr++;
 	}
     }
@@ -2302,6 +2316,28 @@ sub saveHandGrade {
 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 		"$env{'user.name'}:$env{'user.domain'}";
 	}
+        if ($env{'form.part'.$new_part.'_returndoc1'}) {
+            # if multiple files are uploaded names will be 'returndoc2','returndoc3'
+           
+            my $portfolio_root = &Apache::loncommon::propath($domain,
+    					 $stuname).
+    					'/userfiles/portfolio';
+                $request->print('<br>'.$portfolio_root.'<br>');
+                
+    #                my $result=&Apache::lonnet::userfileupload('uploaddoc','',
+    #	        	 'portfolio'.$env{'form.currentpath'});
+    
+            my $file_counter = 1;
+            my $respid = $env{'form.respid'};
+            while ($env{'form.part'.$new_part.'_returndoc'.$file_counter}) {
+                my $fname=$env{'form.returndoc'.$file_counter.'.filename'};
+                $newrecord{"resource.$new_part.$respid.handback"} = $env{'form.returndocorig'.$file_counter};
+                $request->print("<br />".$fname." will be the uploaded file name");
+                $request->print("<font color=\"red\">Will upload document</font>".$env{'form.returndocorig'.$file_counter});
+                $file_counter++;
+            }
+        }
+	
 	# unless problem has been graded, set flag to version the submitted files
 	unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || 
 	        $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
@@ -2338,7 +2374,20 @@ sub saveHandGrade {
     }
     return ('',$pts,$wgt);
 }
-
+sub get_submitted_files {
+    my ($udom,$uname,$partid,$respid,$record) = @_;
+    my @files;
+    if ($$record{"resource.$partid.$respid.portfiles"}) {
+        my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
+        foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
+    	    push(@files,$file_url.$file);
+        }
+    }
+    if ($$record{"resource.$partid.$respid.uploadedurl"}) {
+        push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
+    }
+    return (\@files);
+}
 # ----------- Provides number of tries since last reset.
 sub get_num_tries {
     my ($record,$last_reset,$part) = @_;
@@ -2418,22 +2467,7 @@ sub version_portfiles {
 		my ($answer_name,$answer_ver,$answer_ext) =
 		    &file_name_version_ext($answer_file);
                 my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);
-                foreach my $row (@dir_list) {
-                    my ($file) = split(/\&/,$row,2);
-		    my ($file_name,$file_version,$file_ext) =
-			&file_name_version_ext($file);
-                    if (($file_name eq $answer_name) && 
-			($file_ext eq $answer_ext)) {
-                        # gets here if filename and extension match, regardless of version
-                        if ($file_version ne '') {
-                            # a versioned file is found  so save it for later
-                            if ($file_version > $version) {
-				$version = $file_version;
-			    }
-                        }
-                    }
-                }
-                $version++;
+                $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                 my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
                 if ($new_answer ne 'problem getting file') {
                     push(@v_portfiles, $directory.$new_answer);
@@ -2449,6 +2483,28 @@ sub version_portfiles {
     return 'ok';   
 }
 
+sub get_next_version {
+    my ($answer_name, $answer_ext, $dir_list);
+    my $version;
+    foreach my $row (@$dir_list) {
+        my ($file) = split(/\&/,$row,2);
+        my ($file_name,$file_version,$file_ext) =
+	    &file_name_version_ext($file);
+        if (($file_name eq $answer_name) && 
+	    ($file_ext eq $answer_ext)) {
+                # gets here if filename and extension match, regardless of version
+                if ($file_version ne '') {
+                # a versioned file is found  so save it for later
+                if ($file_version > $version) {
+		    $version = $file_version;
+	        }
+            }
+        }
+    } 
+    $version ++;
+    return($version);
+}
+
 sub version_selected_portfile {
     my ($domain,$stu_name,$directory,$file_name,$version) = @_;
     my ($answer_name,$answer_ver,$answer_ext) =
@@ -2837,7 +2893,7 @@ sub viewstudentgrade {
             $aggregates{$part} = 1;
         }
 	if ($type eq 'awarded') {
-	    my $pts = $score eq '' ? '' : $score*$$weight{$part};
+	    my $pts = $score eq '' ? '' : compute_points($score,$$weight{$part});
 	    $result.='<input type="hidden" name="'.
 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
 	    $result.='<input type="text" name="'.
@@ -3319,8 +3375,13 @@ sub csvuploadmap {
 	    unshift(@fields,['none','']);
 	    $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
 							    \@fields);
-	    my %sone=&Apache::loncommon::record_sep($records[0]);
-	    $keyfields=join(',',sort(keys(%sone)));
+            foreach my $rec (@records) {
+                my %temp = &Apache::loncommon::record_sep($rec);
+                if (%temp) {
+                    $keyfields=join(',',sort(keys(%temp)));
+                    last;
+                }
+            }
 	}
     }
     &csvuploadmap_footer($request,$i,$keyfields);
@@ -4086,11 +4147,11 @@ sub scantron_CODElist {
 sub scantron_CODEunique {
     my $result='<nobr>
                  <label><input type="radio" name="scantron_CODEunique"
-                        value="Yes" checked="on" /> Yes </label>
+                        value="yes" checked="checked" /> Yes </label>
                 </nobr>
                 <nobr>
                  <label><input type="radio" name="scantron_CODEunique"
-                        value="No" /> No </label>
+                        value="no" /> No </label>
                 </nobr>';
     return $result;
 }