--- loncom/homework/grades.pm 2007/07/24 21:21:31 1.423 +++ loncom/homework/grades.pm 2007/09/07 00:07:30 1.439 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.423 2007/07/24 21:21:31 albertel Exp $ +# $Id: grades.pm,v 1.439 2007/09/07 00:07:30 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -45,36 +45,26 @@ 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.---- # # --- Retrieve the parts from the metadata file.--- sub getpartlist { my ($symb) = @_; - my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); - my $partorder = &Apache::lonnet::metadata($url, 'partorder'); - my @parts; - if ($partorder) { - for my $part (split (/,/,$partorder)) { - if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) { - push(@parts, $part); - } - } - } else { - my $metadata = &Apache::lonnet::metadata($url, 'packages'); - foreach (split(/\,/,$metadata)) { - if ($_ =~ /^part_(.*)$/) { - if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) { - push(@parts, $1); - } - } - } - } + + my $navmap = Apache::lonnavmaps::navmap->new(); + my $res = $navmap->getBySymb($symb); + my $partlist = $res->parts(); + my $url = $res->src(); + my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys')); + my @stores; - foreach my $part (@parts) { - my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys')); + foreach my $part (@{ $partlist }) { foreach my $key (@metakeys) { if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); } } @@ -195,22 +185,54 @@ sub showResourceInfo { return $result,$responseType,$hdgrade,$partlist,$handgrade; } +sub reset_caches { + &reset_analyze_cache(); + &reset_perm(); +} -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"}); +{ + my %analyze_cache; + + 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 +281,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.='true'; } else { $toprow.='true'; @@ -538,7 +560,7 @@ sub compute_points { # sub most_similar { - my ($uname,$udom,$uessay)=@_; + my ($uname,$udom,$uessay,$old_essays)=@_; # ignore spaces and punctuation @@ -555,23 +577,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 +746,6 @@ LISTJAVASCRIPT my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'}; $env{'form.Status'} = $saveStatus; - $gradeTable.=''."\n". ''."\n". ''."\n". @@ -736,8 +756,7 @@ LISTJAVASCRIPT ''. ''. ''. - - ''."\n". + &build_section_inputs(). ''."\n". '
'."\n". '
'."\n". @@ -1663,6 +1682,19 @@ sub download_all_link { return } +sub build_section_inputs { + my $section_inputs; + if ($env{'form.section'} eq '') { + $section_inputs .= ''."\n"; + } else { + my @sections = &Apache::loncommon::get_env_multiple('form.section'); + foreach my $section (@sections) { + $section_inputs .= ''."\n"; + } + } + return $section_inputs; +} + # --------------------------- show submissions of a student, option to grade sub submission { my ($request,$counter,$total) = @_; @@ -1691,6 +1723,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 +1775,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('
'."\n". ''."\n". ''."\n". @@ -1757,7 +1789,7 @@ sub submission { ''."\n". ''."\n". ''."\n". - ''."\n". + &build_section_inputs(). ''."\n". ''."\n". '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="

". + &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'}). '

'. &keywords_highlight($oessay). '

'; @@ -2957,20 +2998,21 @@ sub viewgrades { $result.= ''."\n". ''."\n". ''."\n". - ''."\n". + &build_section_inputs(). ''."\n". ''."\n". ''."\n"; my $sectionClass; + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); if ($env{'form.section'} eq 'all') { $sectionClass='Class '; } elsif ($env{'form.section'} eq 'none') { - $sectionClass='Students in no Section '; + $sectionClass=&mt('Students in no Section').''; } else { - $sectionClass='Students in Section '.$env{'form.section'}.''; + $sectionClass=&mt('Students in Section(s) [_1]',$section_display).''; } - $result.='

Assign Common Grade To '.$sectionClass; + $result.='

'.&mt('Assign Common Grade To [_1]',$sectionClass); $result.= '
'."\n". '
'; #radio buttons/text box for assigning points for a section or class. @@ -3076,8 +3118,11 @@ sub viewgrades { 'onClick="javascript:submit();" target="_self" />'."\n"; if (scalar(%$fullname) eq 0) { my $colspan=3+scalar(@parts); - $result='There are no students in section "'.$env{'form.section'}. - '" with enrollment status "'.$env{'form.Status'}.'" to modify or grade.'; + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); + $result=''. + &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade', + $section_display, $env{'form.Status'}). + ''; } $result.=&show_grading_menu_form($symb); return $result; @@ -3154,9 +3199,10 @@ sub editgrades { my ($request) = @_; my $symb=&get_symb($request); - my $title='

Current Grade Status

'; - $title.='

Current Resource: '.$env{'form.probTitle'}.'


'."\n"; - $title.='

Section: '.$env{'form.section'}.'

'."\n"; + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); + my $title='

'.&mt('Current Grade Status').'

'; + $title.='

'.&mt('Current Resource: [_1]',$env{'form.probTitle'}).'


'."\n"; + $title.='

'.&mt('Section: [_1]',$section_display).'

'."\n"; my $result= '
'."\n"; $result.= ''. @@ -3354,7 +3400,7 @@ sub split_part_type { my ($partstr) = @_; my ($temp,@allparts)=split(/_/,$partstr); my $type=pop(@allparts); - my $part=join('.',@allparts); + my $part=join('_',@allparts); return ($part,$type); } @@ -3857,9 +3903,9 @@ LISTJAVASCRIPT ''."\n". ''."\n". ''."\n"; - - $result.=''."\n". - ''."\n". + + $result.=&build_section_inputs(); + $result.=''."\n". ''."\n". ''."\n". ''."
\n"; @@ -4357,9 +4403,47 @@ sub updateGradeByPage { =head1 Bubble sheet grading routines - (For this documentation 'scanline' refers to the full line of characters - from the file that we are parsing 'bubble line' refers to the data - representing the line of bubbles that are on the physical bubble sheet) + 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 @@ -4531,7 +4615,7 @@ sub scantron_CODEunique { Generates the initial screen to start the bubble sheet process. Allows for - starting a grading run. - - downloading exisiting scan data (original, corrected + - downloading existing scan data (original, corrected or skipped info) - uploading new scan data @@ -4589,7 +4673,7 @@ sub scantron_selectphase { @@ -4732,17 +4816,18 @@ SCANTRONFORM 'questions' start Qlength - number of columns comprising a single bubble line from the sheet. (usually either 1 or 10) - Qon - either a single charater representing the character used + Qon - either a single character representing the character used to signal a bubble was chosen in the positional setup, or the string 'letter' if the letter of the chosen bubble is in the final, or 'number' if a number representing the chosen bubble is in the file (1->A 0->J) - Qoff - the character used to represent that a buble was left blank + Qoff - the character used to represent that a bubble was + left blank PaperID - if the scanning process generates a unique number for each sheet scanned the column that this ID number starts in PaperIDlength - number of columns that comprise the unique ID number for the sheet of paper - FirstName - column that the firs tname starts in + FirstName - column that the first name starts in FirstNameLength - number of columns that the first name spans LastName - column that the last name starts in @@ -4793,7 +4878,7 @@ sub get_scantron_config { $classlist - reference to the class list hash. This is a hash keyed by student name:domain whose elements are references - to arrays containng various chunks of information + to arrays containing various chunks of information about the student. (See loncoursedata for more info). Returns @@ -4813,7 +4898,7 @@ sub username_to_idmap { =pod -=item scatron_fixup_scanline +=item scantron_fixup_scanline Process a requested correction to a scanline. @@ -4832,12 +4917,12 @@ sub username_to_idmap { $args - hash of additional info, - 'ID' 'newid' -> studentID to use in replacement - of exisiting one + of existing one - 'CODE' 'CODE_ignore_dup' - set to true if duplicates should be ignored. 'CODE' - is new code or 'use_unfound' - if the exisitng unfound code should + if the existing unfound code should be used as is - 'answer' 'response' - new answer or 'none' if blank @@ -4920,7 +5005,7 @@ sub scantron_fixup_scanline { Arguments: $scan_data - The hash (see scantron_getfile) $key - shorthand of the key to edit (actual key is - scatronfilename_key). + scantronfilename_key). $data - New value of the hash entry. $delete - If true, the entry is removed from the hash. @@ -5107,13 +5192,13 @@ sub scantron_parse_scanline { queue of messages to be shown after grading pass is complete Arguments: - $delayqueue - arrary ref of hash ref of erro messages + $delayqueue - arrary ref of hash ref of error messages $scanline - the scanline that caused the error $errormesage - the error message $errorcode - a numeric code for the error Side Effects: - updates the $dealyqueue to have a new hash ref of the error + updates the $delayqueue to have a new hash ref of the error =cut @@ -5129,6 +5214,18 @@ sub scantron_add_delay { =item scantron_find_student + Finds the username for the current scanline + + Arguments: + $scantron_record - hash result from scantron_parse_scanline + $scan_data - hash of correction information + (see &scantron_getfile() form more information) + $idmap - hash from &username_to_idmap() + $line - number of current scanline + + Returns: + Either 'username:domain' or undef if unknown + =cut sub scantron_find_student { @@ -5149,6 +5246,9 @@ sub scantron_find_student { =item scantron_filter + Filter sub for lonnavmaps, filters out hidden resources if ignore + hidden resources was selected + =cut sub scantron_filter { @@ -5171,6 +5271,9 @@ sub scantron_filter { =item scantron_process_corrections + Gets correction information out of submitted form data and corrects + the scanline + =cut sub scantron_process_corrections { @@ -5234,6 +5337,10 @@ sub scantron_process_corrections { =item reset_skipping_status + Forgets the current set of remember skipped scanlines (and thus + reverts back to considering all lines in the + scantron_skipped_ file) + =cut sub reset_skipping_status { @@ -5246,6 +5353,8 @@ sub reset_skipping_status { =item start_skipping + Marks a scanline to be skipped. + =cut sub start_skipping { @@ -5263,6 +5372,8 @@ sub start_skipping { =item should_be_skipped + Checks whether a scanline should be skipped. + =cut sub should_be_skipped { @@ -5284,6 +5395,9 @@ sub should_be_skipped { =item remember_current_skipped + Discovers what scanlines are in the scantron_skipped_ + file and remembers them into scan_data for later use. + =cut sub remember_current_skipped { @@ -5303,6 +5417,10 @@ sub remember_current_skipped { =item check_for_error + Checks if there was an error when attempting to remove a specific + scantron_.. bubble sheet data file. Prints out an error if + something went wrong. + =cut sub check_for_error { @@ -5316,6 +5434,9 @@ sub check_for_error { =item scantron_warning_screen + Interstitial screen to make sure the operator has selected the + correct options before we start the validation phase. + =cut sub scantron_warning_screen { @@ -5354,6 +5475,9 @@ STUFF =item scantron_do_warning + Check if the operator has picked something for all required + fields. Error out if something is missing. + =cut sub scantron_do_warning { @@ -5391,6 +5515,8 @@ STUFF =item scantron_form_start + html hidden input for remembering all selected grading options + =cut sub scantron_form_start { @@ -5414,6 +5540,12 @@ SCANTRONFORM =item scantron_validate_file + Dispatch routine for doing validation of a bubble sheet data file. + + Also processes any necessary information resets that need to + occur before validation begins (ignore previous corrections, + restarting the skipped records processing) + =cut sub scantron_validate_file { @@ -5423,7 +5555,7 @@ sub scantron_validate_file { my $default_form_data=&defaultFormData($symb); # do the detection of only doing skipped records first befroe we delete - # them when doing the corrections reset + # them when doing the corrections reset if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') { &reset_skipping_status(); } @@ -5442,7 +5574,7 @@ sub scantron_validate_file { if ($env{'form.scantron_corrections'}) { &scantron_process_corrections($r); } - $r->print("

Gathering neccessary info.

");$r->rflush(); + $r->print("

Gathering necessary info.

");$r->rflush(); #get the student pick code ready $r->print(&Apache::loncommon::studentbrowser_javascript()); my $max_bubble=&scantron_get_maxbubble(); @@ -5504,6 +5636,10 @@ STUFF =item scantron_remove_file + Removes the requested bubble sheet data file, makes sure that + scantron_original_ is never removed + + =cut sub scantron_remove_file { @@ -5525,6 +5661,11 @@ sub scantron_remove_file { =item scantron_remove_scan_data + Removes all scan_data correction for the requested bubble sheet + data file. (In the case that both the are doing skipped records we need + to remember the old skipped lines for the time being so that element + persists for a while.) + =cut sub scantron_remove_scan_data { @@ -5554,6 +5695,44 @@ sub scantron_remove_scan_data { =item scantron_getfile + Fetches the requested bubble sheet data file (all 3 versions), and + the scan_data hash + + Arguments: + None + + Returns: + 2 hash references + + - first one has + orig - + corrected - + skipped - each of which points to an array ref of the specified + file broken up into individual lines + 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 sub scantron_getfile { @@ -5592,6 +5771,15 @@ sub scantron_getfile { =item lonnet_putfile + Wrapper routine to call &Apache::lonnet::finishuserfileupload + + Arguments: + $contents - data to store + $filename - filename to store $contents into + + Returns: + result value from &Apache::lonnet::finishuserfileupload + =cut sub lonnet_putfile { @@ -5607,6 +5795,16 @@ sub lonnet_putfile { =item scantron_putfile + Stores the current version of the bubble sheet data files, and the + scan_data hash. (Does not modify the original version only the + corrected and skipped versions. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + =cut sub scantron_putfile { @@ -5633,6 +5831,22 @@ sub scantron_putfile { =item scantron_get_line + Returns the correct version of the scanline + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - number of the requested line (starts at 0) + + Returns: + A scanline, (either the original or the corrected one if it + exists), or undef if the requested scanline should be + skipped. (Either because it's an skipped scanline, or it's an + unskipped scanline and we are not doing a 'do skipped scanlines' + pass. + =cut sub scantron_get_line { @@ -5647,6 +5861,17 @@ sub scantron_get_line { =item scantron_todo_count + Counts the number of scanlines that need processing. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + + Returns: + $count - number of scanlines to process + =cut sub get_todo_count { @@ -5664,6 +5889,19 @@ sub get_todo_count { =item scantron_put_line + Updates the 'corrected' or 'skipped' versions of the bubble sheet + data file. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - line number to update + $newline - contents of the updated scanline + $skip - if true make the line for skipping and update the + 'skipped' file + =cut sub scantron_put_line { @@ -5680,6 +5918,15 @@ sub scantron_put_line { =item scantron_clear_skip + Remove a line from the 'skipped' file + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - line number to update + =cut sub scantron_clear_skip { @@ -5695,6 +5942,9 @@ sub scantron_clear_skip { =item scantron_filter_not_exam + Filter routine used by &Apache::lonnavmaps::retrieveResources(), to + filter out resources that are not marked as 'exam' mode + =cut sub scantron_filter_not_exam { @@ -5717,6 +5967,9 @@ sub scantron_filter_not_exam { =item scantron_validate_sequence + Validates the selected sequence, checking for resource that are + not set to exam mode. + =cut sub scantron_validate_sequence { @@ -5746,6 +5999,9 @@ sub scantron_validate_sequence { =item scantron_validate_ID + Validates all scanlines in the selected file to not have any + invalid or underspecified student IDs + =cut sub scantron_validate_ID { @@ -5814,6 +6070,30 @@ sub scantron_validate_ID { =item scantron_get_correction + Builds the interface screen to interact with the operator to fix a + specific error condition in a specific scanline + + Arguments: + $r - Apache request object + $i - number of the current scanline + $scan_record - hash ref as returned from &scantron_parse_scanline() + $scan_config - hash ref as returned from &get_scantron_config() + $line - full contents of the current scanline + $error - error condition, valid values are + 'incorrectCODE', 'duplicateCODE', + 'doublebubble', 'missingbubble', + 'duplicateID', 'incorrectID' + $arg - extra information needed + For errors: + - duplicateID - paper number that this studentID was seen before on + - duplicateCODE - array ref of the paper numbers this CODE was + seen on before + - incorrectCODE - current incorrect CODE + - doublebubble - array ref of the bubble lines that have double + bubble errors + - missingbubble - array ref of the bubble lines that have missing + bubble errors + =cut sub scantron_get_correction { @@ -5945,7 +6225,7 @@ ENDSCRIPT =item scantron_bubble_selector Generates the html radiobuttons to correct a single bubble line - possibly showing the exisiting the selected bubbles if known + possibly showing the existing the selected bubbles if known Arguments: $r - Apache request object @@ -6021,6 +6301,16 @@ sub scantron_bubble_selector { =item num_matches + Counts the number of characters that are the same between the two arguments. + + Arguments: + $orig - CODE from the scanline + $code - CODE to match against + + Returns: + $count - integer count of the number of same characters between the + two arguments + =cut sub num_matches { @@ -6038,6 +6328,20 @@ sub num_matches { =item scantron_get_closely_matching_CODEs + Cycles through all CODEs and finds the set that has the greatest + number of same characters as the provided CODE + + Arguments: + $allcodes - hash ref returned by &get_codes() + $CODE - CODE from the current scanline + + Returns: + 2 element list + - first elements is number of how closely matching the best fit is + (5 means best set has 5 matching characters) + - second element is an arrary ref containing the set of valid CODEs + that best fit the passed in CODE + =cut sub scantron_get_closely_matching_CODEs { @@ -6054,6 +6358,17 @@ sub scantron_get_closely_matching_CODEs =item get_codes + Builds a hash which has keys of all of the valid CODEs from the selected + set of remembered CODEs. + + Arguments: + $old_name - name of the set of remembered CODEs + $cdom - domain of the course + $cnum - internal course name + + Returns: + %allcodes - keys are the valid CODEs, values are all 1 + =cut sub get_codes { @@ -6082,6 +6397,10 @@ sub get_codes { =item scantron_validate_CODE + Validates all scanlines in the selected file to not have any + invalid or underspecified CODEs and that none of the codes are + duplicated if this was requested. + =cut sub scantron_validate_CODE { @@ -6139,6 +6458,9 @@ sub scantron_validate_CODE { =item scantron_validate_doublebubble + Validates all scanlines in the selected file to not have any + bubble lines with multiple bubbles marked. + =cut sub scantron_validate_doublebubble { @@ -6168,9 +6490,17 @@ sub scantron_validate_doublebubble { =item scantron_get_maxbubble + Returns the maximum number of bubble lines that are expected to + occur. Does this by walking the selected sequence rendering the + 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'} + =cut sub scantron_get_maxbubble { + if (defined($env{'form.scantron_maxbubble'}) && $env{'form.scantron_maxbubble'}) { return $env{'form.scantron_maxbubble'}; @@ -6185,14 +6515,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'}; } @@ -6200,6 +6556,9 @@ sub scantron_get_maxbubble { =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. + =cut sub scantron_validate_missingbubbles { @@ -6474,7 +6833,7 @@ sub scantron_upload_scantron_data_save { =item valid_file - Vaildates that the requested bubble data file has exists in the course. + Validates that the requested bubble data file exists in the course. =cut @@ -6638,7 +6997,7 @@ GRADINGMENUJS $result.='
Options:
-
+
'; $result.='
'."\n". - ' '.&mt('Select Section').': '."\n"; if (ref($sections)) { foreach (sort (@$sections)) { $result.='