[LON-CAPA-cvs] cvs: loncom /homework grades.pm
foxr
lon-capa-cvs-allow@mail.lon-capa.org
Tue, 09 Oct 2007 09:16:08 -0000
This is a MIME encoded message
--foxr1191921368
Content-Type: text/plain
foxr Tue Oct 9 05:16:08 2007 EDT
Modified files:
/loncom/homework grades.pm
Log:
Removed >my< log spew too, .. saving work on BZ 4074 not yet done..
resolved conflict with banghart's last commit.
--foxr1191921368
Content-Type: text/plain
Content-Disposition: attachment; filename="foxr-20071009051608.txt"
Index: loncom/homework/grades.pm
diff -u loncom/homework/grades.pm:1.446 loncom/homework/grades.pm:1.447
--- loncom/homework/grades.pm:1.446 Mon Oct 8 18:29:59 2007
+++ loncom/homework/grades.pm Tue Oct 9 05:16:04 2007
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.446 2007/10/08 22:29:59 banghart Exp $
+# $Id: grades.pm,v 1.447 2007/10/09 09:16:04 foxr Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -47,11 +47,88 @@
my %perm=();
-my %bubble_lines_per_response; # no. bubble lines for each response.
+my %bubble_lines_per_response = (); # no. bubble lines for each response.
# index is "symb.part_id"
+my %first_bubble_line = (); # First bubble line no. for each bubble.
+
+# Save and restore the bubble lines array to the form env.
+
+
+sub save_bubble_lines {
+
+ foreach my $line (keys(%bubble_lines_per_response)) {
+ $env{"form.scantron.bubblelines.$line"} = $bubble_lines_per_response{$line};
+ $env{"form.scantron.first_bubble_line.$line"} =
+ $first_bubble_line{$line};
+ }
+}
+
+
+sub restore_bubble_lines {
+ my $line = 0;
+ %bubble_lines_per_response = ();
+ while ($env{"form.scantron.bubblelines.$line"}) {
+ my $value = $env{"form.scantron.bubblelines.$line"};
+ $bubble_lines_per_response{$line} = $value;
+ $first_bubble_line{$line} =
+ $env{"form.scantron.first_bubble_line.$line"};
+ $line++;
+ }
+
+}
+
+# Given the parsed scanline, get the response for
+# 'answer' number n:
+
+sub get_response_bubbles {
+ my ($parsed_line, $response) = @_;
+
+ my $bubble_line = $first_bubble_line{$response};
+ my $bubble_lines= $bubble_linse_per_response{$response};
+ my $selected = "";
+
+ for (my $bline = 0; $bline < $bubble_lines; $bline++) {
+ $selected .= $$parsed_line{"scantron.$bubble_line.answer"};
+ $bubble_line++;
+ }
+ return $selected;
+}
+
# ----- These first few routines are general use routines.----
+
+# Return the number of occurences of a pattern in a string.
+
+sub occurence_count {
+ my ($string, $pattern) = @_;
+
+ my @matches = ($string =~ /$pattern/g);
+
+ return scalar(@matches);
+}
+
+
+# Take a string known to have digits and convert all the
+# digits into letters in the range J,A..I.
+
+sub digits_to_letters {
+ my ($input) = @_;
+
+ my @alphabet = ('J', 'A'..'I');
+
+ my @input = split(//, $input);
+ my $output ='';
+ for (my $i = 0; $i < scalar(@input); $i++) {
+ if ($input[$i] =~ /\d/) {
+ $output .= $alphabet[$input[$i]];
+ } else {
+ $output .= $input[$i];
+ }
+ }
+ return $output;
+}
+
#
# --- Retrieve the parts from the metadata file.---
sub getpartlist {
@@ -531,6 +608,8 @@
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.
@@ -2789,7 +2868,6 @@
my $new_answer;
$env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
if($env{'form.copy'} eq '-1') {
- &Apache::lonnet::logthis('problem getting file '.$file_name);
$new_answer = 'problem getting file';
} else {
$new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
@@ -3823,7 +3901,6 @@
}
if (! %grades) { push(@skipped,"$username:$domain no data to save"); }
$grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
-# &Apache::lonnet::logthis(" storing ".(join('-',%grades)));
my $result=&Apache::lonnet::cstore(\%grades,$symb,
$env{'request.course.id'},
$domain,$username);
@@ -4176,7 +4253,6 @@
my $where = ($isTask ? "$version:resource.$interaction"
: "$version:resource");
- #&Apache::lonnet::logthis(" got $where");
$studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';
if ($isCODE) {
$studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
@@ -4196,7 +4272,6 @@
my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
: ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
- #&Apache::lonnet::logthis("match $matchKey $responseId (".$$record{$version.':'.$matchKey});
$displaySub[0].='<b>Part:</b> '.$display_part.' ';
$displaySub[0].='<span class="LC_internal_info">(ID '.
$responseId.')</span> <b>';
@@ -4465,10 +4540,7 @@
=over 4
-=cut
-
-=pod
=item defaultFormData
@@ -4481,12 +4553,12 @@
sub defaultFormData {
my ($symb)=@_;
- return '
- <input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
+ return '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
}
+
=pod
=item getSequenceDropDown
@@ -5076,13 +5148,18 @@
if just_header was not true these key may also exist
- missingerror - a list of bubbled line numbers that had a blank bubble
- that is considered an error (if the operator had already
- okayed a blank bubble line as really being blank then
- that bubble line number won't appear here.
- doubleerror - a list of bubbled line numbers that had more than one
- bubble filled in and has not been corrected by the
- operator
+ missingerror - a list of bubble ranges that are considered to be answers
+ to a single question that don't have any bubbles filled in.
+ Of the form questionnumber:firstbubblenumber:count.
+ doubleerror - a list of bubble ranges that are considered to be answers
+ to a single question that have more than one bubble filled in.
+ Of the form questionnumber::firstbubblenumber:count
+
+ In the above, count is the number of bubble responses in the
+ input line needed to represent the possible answers to the question.
+ e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
+ per line would have count = 2.
+
maxquest - the number of the last bubble line that was parsed
(<number> starts at 1)
@@ -5133,66 +5210,149 @@
my @alphabet=('A'..'Z');
my $questnum=0;
+ my $ansnum =1; # Multiple 'answer lines'/question.
+
while ($questions) {
+ my $answers_needed = $bubble_lines_per_response{$questnum};
+ my $answer_length = $$scantron_config{'Qlength'} * $answers_needed;
+
+
+
$questnum++;
- my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});
- substr($questions,0,$$scantron_config{'Qlength'})='';
- if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }
+ my $currentquest = substr($questions,0,$answer_length);
+ $questions = substr($questions,0,$answer_length)='';
+ if (length($currentquest) < $answer_length) { next; }
+
+ # Qon letter implies for each slot in currentquest we have:
+ # ? or * for doubles a letter in A-Z for a bubble and
+ # about anything else (esp. a value of Qoff for missing
+ # bubbles.
+
+
if ($$scantron_config{'Qon'} eq 'letter') {
- if ($currentquest eq '?'
- || $currentquest eq '*') {
+
+ if ($currentquest =~ /\?/
+ || $currentquest =~ /\*/
+ || (&occurence_count($currentquest, "[A-Z]") > 1)) {
push(@{$record{'scantron.doubleerror'}},$questnum);
- $record{"scantron.$questnum.answer"}='';
+ for (my $ans = 0; $ans < $answers_needed; $ans++) {
+ $record{"scantron.$ansnum.answer"}='';
+ $ansnum++;
+ }
+
} elsif (!defined($currentquest)
- || $currentquest eq $$scantron_config{'Qoff'}
- || $currentquest !~ /^[A-Z]$/) {
- $record{"scantron.$questnum.answer"}='';
+ || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest))
+ || (&occurence_count($currentquest, "[A-Z]") == 0)) {
+ for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
+ $record{"scantron.$ansnum.answer"}='';
+ $ansnum++;
+
+ }
if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
push(@{$record{"scantron.missingerror"}},$questnum);
+ $ansnum += $answers_needed;
}
+
} else {
- $record{"scantron.$questnum.answer"}=$currentquest;
+ for (my $ans = 0; $ans < $answers_needed; $ans++) {
+ $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
+ $ansnum++;
+ }
}
+
+ # Qon 'number' implies each slot gives a digit that indexes the
+ # the bubbles filled or Qoff or a non number for unbubbled lines.
+ # and *? for double bubbles on a line.
+ # these answers are also stored as letters.
+
} elsif ($$scantron_config{'Qon'} eq 'number') {
- if ($currentquest eq '?'
- || $currentquest eq '*') {
+ if ($currentquest =~ /\?/
+ || $currentquest =~ /\*/
+ || (&occurence_count($currentquest, '\d') > 1)) {
push(@{$record{'scantron.doubleerror'}},$questnum);
- $record{"scantron.$questnum.answer"}='';
+ for (my $ans = 0; $ans < $answers_needed; $ans++) {
+ $record{"scantron.$ansnum.answer"}='';
+ $ansnum++;
+ }
+
} elsif (!defined($currentquest)
- || $currentquest eq $$scantron_config{'Qoff'}
- || $currentquest !~ /^\d$/) {
- $record{"scantron.$questnum.answer"}='';
+ || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest))
+ || (&occurence_count($currentquest, '\d') == 0)) {
+ for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
+ $record{"scantron.$ansnum.answer"}='';
+ $ansnum++;
+
+ }
if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
push(@{$record{"scantron.missingerror"}},$questnum);
+ $ansnum += $answers_needed;
}
+
} else {
- # wrap zero back to J
- if ($currentquest eq '0') {
- $record{"scantron.$questnum.answer"}=
- $alphabet[9];
- } else {
- $record{"scantron.$questnum.answer"}=
- $alphabet[$currentquest-1];
+ $currentquest = &digits_to_letters($currentquest);
+ for (my $ans =0; $ans < $answers_needed; $ans++) {
+ $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
+ $ansnum++;
}
}
} else {
+
+ # Otherwise there's a positional notation;
+ # each bubble line requires Qlength items, and there are filled in
+ # bubbles for each case where there 'Qon' characters.
+ #
+
my @array=split($$scantron_config{'Qon'},$currentquest,-1);
- if (length($array[0]) eq $$scantron_config{'Qlength'}) {
- $record{"scantron.$questnum.answer"}='';
+
+ # If the split only giveas us one element.. the full length of the
+ # answser string, no bubbles are filled in:
+
+ if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
+ for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
+ $record{"scantron.$ansnum.answer"}='';
+ $ansnum++;
+
+ }
if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
push(@{$record{"scantron.missingerror"}},$questnum);
}
- } else {
- $record{"scantron.$questnum.answer"}=
- $alphabet[length($array[0])];
+ } elsif (scalar(@array) lt 2) {
+
+ my $location = [length($array[0])];
+ my $line_num = $location / $$scantron_config{'Qlength'};
+ my $bubble = $alphabet[$location % $$scantron_config{'Qlength'}];
+
+ for (my $ans = 0; $ans < $answers_needed; $ans++) {
+ if ($ans eq $line_num) {
+ $record{"scantron.$ansnum.answer"} = $bubble;
+ } else {
+ $record{"scantron.$ansnum.answer"} = ' ';
+ }
+ $ansnum++;
+ }
}
- if (scalar(@array) gt 2) {
+ # If there's more than one instance of a bubble character
+ # That's a double bubble; with positional notation we can
+ # record all the bubbles filled in as well as the
+ # fact this response consists of multiple bubbles.
+ #
+ else {
push(@{$record{'scantron.doubleerror'}},$questnum);
+
+ my $first_answer = $ansnum;
+ for (my $ans =0; $ans < $answers_needed; $ans++) {
+ $record{"scantron.$ansnum.answer"} = '';
+ $ans++;
+ }
+
my @ans=@array;
my $i=length($ans[0]);shift(@ans);
while ($#ans) {
$i+=length($ans[0])+1;
- $record{"scantron.$questnum.answer"}.=$alphabet[$i];
+ my $line = $i/$$scantron_config{'Qlength'} + $first_answer;
+ my $bubble = $i%$$scantron_config{'Qlength'};
+
+ $record{"scantron.$line.answer"}.=$alphabet[$bubble];
shift(@ans);
}
}
@@ -5551,6 +5711,16 @@
<input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
<input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
SCANTRONFORM
+
+ my $line = 0;
+ while (defined($env{"form.scantron.bubblelines.$line"})) {
+ my $chunk =
+ '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
+ $chunk +=
+ '<input type="hidden" name="scantron.first_bubble_line.'.$line'." value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
+ $result .= $chunk;
+ $line++;
+ }
return $result;
}
@@ -6032,6 +6202,8 @@
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
+
+ &scantron_get_maxbubble(); # parse needs the bubble_lines.. array.
my %found=('ids'=>{},'usernames'=>{});
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
@@ -6216,7 +6388,8 @@
$r->print($message);
$r->print("<p>Please indicate which bubble should be used for grading</p>");
foreach my $question (@{$arg}) {
- my $selected=$$scan_record{"scantron.$question.answer"};
+
+ my $selected = &get_response_bubbles($scan_record, $question);
&scantron_bubble_selector($r,$scan_config,$question,
split('',$selected));
}
@@ -6228,7 +6401,7 @@
$r->print('<input type="hidden" name="scantron_questions" value="'.
join(',',@{$arg}).'" />');
foreach my $question (@{$arg}) {
- my $selected=$$scan_record{"scantron.$question.answer"};
+ my $selected = &get_response_bubbles($scan_record, $quesion);
&scantron_bubble_selector($r,$scan_config,$question);
}
} else {
@@ -6250,21 +6423,21 @@
$scan_config - hash from &get_scantron_config()
$quest - number of the bubble line to make a corrector for
$selected - array of letters of previously selected bubbles
- $lines - if present, number of bubble lines to show
=cut
sub scantron_bubble_selector {
- my ($r,$scan_config,$quest,@selected, $lines)=@_;
+ my ($r,$scan_config,$quest,@selected)=@_;
my $max=$$scan_config{'Qlength'};
my $scmode=$$scan_config{'Qon'};
+
+
if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }
- if (!defined($lines)) {
- $lines = 1;
- }
+ my $lines = $bubble_lines_per_response{$quest};
+
my $total_lines = $lines*2;
my @alphabet=('A'..'Z');
$r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");
@@ -6438,6 +6611,8 @@
my %allcodes=&get_codes();
+ &scantron_get_maxbubble(); # parse needs the lines per response array.
+
my ($scanlines,$scan_data)=&scantron_getfile();
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
my $line=&scantron_get_line($scanlines,$scan_data,$i);
@@ -6490,6 +6665,9 @@
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
+
+ &scantron_get_maxbubble(); # parse needs the bubble line array.
+
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
my $line=&scantron_get_line($scanlines,$scan_data,$i);
if ($line=~/^[\s\cz]*$/) { next; }
@@ -6513,7 +6691,11 @@
resource and then checking &Apache::lonxml::get_problem_counter()
for what the current value of the problem counter is.
- Caches the result to $env{'form.scantron_maxbubble'}
+ Caches the results to $env{'form.scantron_maxbubble'},
+ $env{'form.scantron.bubble_lines.n'} and
+ $env{'form.scantron.first_bubble_line.n'}
+ which are the total number of bubble, lines, the number of bubble
+ lines for reponse n and number of the first bubble line for response n.
=cut
@@ -6521,13 +6703,14 @@
if (defined($env{'form.scantron_maxbubble'}) &&
$env{'form.scantron_maxbubble'}) {
+ &restore_bubble_lines();
return $env{'form.scantron_maxbubble'};
}
- my $navmap=Apache::lonnavmaps::navmap->new();
- my (undef,undef,$sequence)=
+ my (undef, undef, $sequence) =
&Apache::lonnet::decode_symb($env{'form.selectpage'});
+ my $navmap=Apache::lonnavmaps::navmap->new();
my $map=$navmap->getResourceByUrl($sequence);
my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
@@ -6538,9 +6721,14 @@
my $cid = $env{'request.course.id'};
my $total_lines = 0;
%bubble_lines_per_response = ();
+ %first_bubble_line = ();
+
+ my $response_number = 0;
+ my $bubble_line = 0;
foreach my $resource (@resources) {
my $symb = $resource->symb();
+ &Apache::lonxml::clear_bubble_lines_for_part();
my $result=&Apache::lonnet::ssi($resource->src(),
('symb' => $resource->symb()),
('grade_target' => 'analyze'),
@@ -6555,16 +6743,25 @@
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;
+ my ($trash, $part) = split(/\./, $part_id);
+
+ my $lines = $analysis{"$part_id.bubble_lines"}[0];
+
+ # TODO - make this a persistent hash not an array.
+
+
+ $first_bubble_line{$response_number} = $bubble_line;
+ $bubble_lines_per_response{$response_number} = $lines;
+ $response_number++;
+
+ $bubble_line += $lines;
+ $total_lines += $lines;
}
}
&Apache::lonnet::delenv('scantron\.');
+
+ &save_bubble_lines();
$env{'form.scantron_maxbubble'} =
$total_lines;
return $env{'form.scantron_maxbubble'};
@@ -6575,7 +6772,8 @@
=item scantron_validate_missingbubbles
Validates all scanlines in the selected file to not have any
- bubble lines with missing bubbles that haven't been verified as missing.
+ answers that don't have bubbles that have not been verified
+ to be bubble free.
=cut
@@ -6669,6 +6867,9 @@
my $start=&Time::HiRes::time();
my $i=-1;
my ($uname,$udom,$started);
+
+ &scantron_get_maxbubble(); # Need the bubble lines array to parse.
+
while ($i<$scanlines->{'count'}) {
($uname,$udom)=('','');
$i++;
@@ -6719,8 +6920,6 @@
}
my $result=&Apache::lonnet::ssi($resource->src(),%form);
if ($result ne '') {
- &Apache::lonnet::logthis("scantron grading error -> $result");
- &Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $env{'request.course.id'} url ".$resource->src());
}
if (&Apache::loncommon::connection_aborted($r)) { last; }
}
@@ -7798,6 +7997,7 @@
sub handler {
my $request=$_[0];
+
&reset_caches();
if ($env{'browser.mathml'}) {
&Apache::loncommon::content_type($request,'text/xml');
@@ -7810,9 +8010,12 @@
my $symb=&get_symb($request,1);
my @commands=&Apache::loncommon::get_env_multiple('form.command');
my $command=$commands[0];
+
if ($#commands > 0) {
&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
}
+
+
$request->print(&Apache::loncommon::start_page('Grading'));
if ($symb eq '' && $command eq '') {
if ($env{'user.adv'}) {
@@ -7890,6 +8093,7 @@
} elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
$request->print(&csvuploadassign($request));
} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
+ &Apache::lonnet::logthis("Selecting pyhase");
$request->print(&scantron_selectphase($request));
} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
$request->print(&scantron_do_warning($request));
--foxr1191921368--