[LON-CAPA-cvs] cvs: doc /loncapafiles loncapafiles.lpml loncom/homework grades.pm inputtags.pm lonhomework.pm structuretags.pm loncom/interface lonnavmaps.pm
raeburn
raeburn at source.lon-capa.org
Thu Aug 21 12:21:42 EDT 2025
raeburn Thu Aug 21 16:21:42 2025 EDT
Modified files:
/loncom/homework lonhomework.pm structuretags.pm inputtags.pm
grades.pm
/loncom/interface lonnavmaps.pm
/doc/loncapafiles loncapafiles.lpml
Log:
- Bug 6623
- Fractional credit for late submission item added to Content Grading Menu
to support instructor modification to points reduction scheme for late
submissions.
- "Current" fractional credit is value based on scheme in effect at
submission time; "New" fractional credit is value based on modified scheme.
- Option to update values for student(s) for whom new differs from current.
- Grades will be sent to launcher CMS for assignments accessed via
LTI-mediated deep-linking for students selected for updates for whom
pass-back is in effect.
-------------- next part --------------
Index: loncom/homework/lonhomework.pm
diff -u loncom/homework/lonhomework.pm:1.397 loncom/homework/lonhomework.pm:1.398
--- loncom/homework/lonhomework.pm:1.397 Wed Aug 13 00:12:11 2025
+++ loncom/homework/lonhomework.pm Thu Aug 21 16:21:41 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Homework handler
#
-# $Id: lonhomework.pm,v 1.397 2025/08/13 00:12:11 raeburn Exp $
+# $Id: lonhomework.pm,v 1.398 2025/08/21 16:21:41 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -760,16 +760,21 @@
}
sub partial_credit_overdue {
- my ($part_id,$symb,$udom,$uname)=@_;
+ my ($part_id,$duedate,$submtime,$symb,$udom,$uname)=@_;
my $reduction;
- my $duedate = &Apache::lonnet::EXT("resource.$part_id.duedate",$symb,
- $udom,$uname);
+ if ($duedate eq '') {
+ $duedate = &Apache::lonnet::EXT("resource.$part_id.duedate",$symb,
+ $udom,$uname);
+ }
if ($duedate) {
my @interval = &Apache::lonnet::EXT("resource.$part_id.interval",$symb);
my $grace = &Apache::lonnet::EXT("resource.$part_id.grace",$symb,
$udom,$uname);
if (($grace) && ($interval[0] !~ /^\d+/)) {
- my $lateness = time - $duedate;
+ if ($submtime eq '') {
+ $submtime = time;
+ }
+ my $lateness = $submtime - $duedate;
if ($lateness > 0) {
my ($start,$end,$startfrac,$endfrac,$usegrad);
$start = 0;
Index: loncom/homework/structuretags.pm
diff -u loncom/homework/structuretags.pm:1.593 loncom/homework/structuretags.pm:1.594
--- loncom/homework/structuretags.pm:1.593 Mon Aug 11 23:33:41 2025
+++ loncom/homework/structuretags.pm Thu Aug 21 16:21:41 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# definition of tags that give a structure to a document
#
-# $Id: structuretags.pm,v 1.593 2025/08/11 23:33:41 raeburn Exp $
+# $Id: structuretags.pm,v 1.594 2025/08/21 16:21:41 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -1348,7 +1348,7 @@
}
}
&Apache::lonxml::debug('Store return message:'.$result);
- &store_aggregates($symb,$courseid);
+ &store_aggregates($symb,$courseid,$domain,$name);
if ($dopassback) {
my $scoreformat = 'decimal';
if (($env{'request.lti.login'}) || ($env{'request.deeplink.login'})) {
@@ -1565,12 +1565,20 @@
Sends hash of values to be incremented in nohist_resourcetracker.db
for the course. Increments total number of attempts, unique students
and corrects for each part for an instance of a problem, as appropriate.
-
+ Adds key=value pair(s), where each key is $symb\0$part and value is 1,
+ to nohist_anonsurveys.db, nohist_randomizetry.db, and nohist_grace.db
+ respectively when storage is being finalized for an anonymous survey,
+ a problem with randomize after N tries, or a submission after the due
+ date when a grace period exists, which has not yet ended. In the case of
+ a late submission with partial credit, a key=value pair will also be
+ stored in nohist_latepenalty.db, where key is username:domain of person
+ for whom submission is being stored, and value is 1.
+
=cut
sub store_aggregates {
- my ($symb,$courseid) = @_;
- my (%aggregate,%anoncounter,%randtrycounter,%gracecounter);
+ my ($symb,$courseid,$udom,$uname) = @_;
+ my (%aggregate,%anoncounter,%randtrycounter,%gracecounter,%latepenalties);
my @parts;
my $cdomain = $env{'course.'.$env{'request.course.id'}.'.domain'};
my $cname = $env{'course.'.$env{'request.course.id'}.'.num'};
@@ -1625,6 +1633,7 @@
($Apache::lonhomework::results{'resource.'.$part.'.latefrac'} < 1) &&
($Apache::lonhomework::results{'resource.'.$part.'.latefrac'} >= 0)) {
$gracecounter{$symb."\0".$part} = 1;
+ $latepenalties{$uname.':'.$udom} = 1;
}
}
if (keys(%aggregate) > 0) {
@@ -1643,6 +1652,10 @@
&Apache::lonnet::cput('nohist_grace',\%gracecounter,
$cdomain,$cname);
}
+ if (keys(%latepenalties) > 0) {
+ &Apache::lonnet::cput('nohist_latepenalty',\%latepenalties,
+ $cdomain,$cname);
+ }
}
sub access_status_msg {
Index: loncom/homework/inputtags.pm
diff -u loncom/homework/inputtags.pm:1.370 loncom/homework/inputtags.pm:1.371
--- loncom/homework/inputtags.pm:1.370 Sat Jun 28 14:35:00 2025
+++ loncom/homework/inputtags.pm Thu Aug 21 16:21:41 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# input definitons
#
-# $Id: inputtags.pm,v 1.370 2025/06/28 14:35:00 raeburn Exp $
+# $Id: inputtags.pm,v 1.371 2025/08/21 16:21:41 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -1628,10 +1628,12 @@
$Apache::lonhomework::results{"resource.$id.maxtries"} = &Apache::lonnet::EXT("resource.$id.maxtries");
if ($Apache::lonhomework::results{"resource.$id.duedate"} ne '') {
my $now = time();
- if ($now > $Apache::lonhomework::results{"resource.$id.duedate"}) {
+ my $pastdue = $now - $Apache::lonhomework::results{"resource.$id.duedate"};
+ if ($pastdue) {
my $overduedate = &Apache::lonhomework::overdue_date($id);
if ($overduedate) {
$Apache::lonhomework::results{"resource.$id.endgrace"} = $overduedate;
+ $Apache::lonhomework::results{"resource.$id.pastdue"} = $pastdue;
if ($now <= $overduedate) {
my $fraction = &Apache::lonhomework::partial_credit_overdue($id);
if ($fraction ne '') {
Index: loncom/homework/grades.pm
diff -u loncom/homework/grades.pm:1.819 loncom/homework/grades.pm:1.820
--- loncom/homework/grades.pm:1.819 Thu Aug 21 15:55:38 2025
+++ loncom/homework/grades.pm Thu Aug 21 16:21:41 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.819 2025/08/21 15:55:38 raeburn Exp $
+# $Id: grades.pm,v 1.820 2025/08/21 16:21:41 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -638,7 +638,7 @@
#--- Dumps the class list with usernames,list of sections,
#--- section, ids and fullnames for each user.
sub getclasslist {
- my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus,$filterbypbid,$possibles) = @_;
+ my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus,$filterbygrace,$filterbypbid,$possibles) = @_;
my @getsec;
my @getgroup;
my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
@@ -782,6 +782,13 @@
next;
}
}
+ } elsif ($filterbygrace) {
+ if (ref($possibles) eq 'HASH') {
+ unless (exists($possibles->{$student})) {
+ delete($classlist->{$student});
+ next;
+ }
+ }
}
$section = ($section ne '' ? $section : 'none');
if (&canview($section)) {
@@ -879,8 +886,6 @@
return $jscript;
}
-
-
# Given the score (as a number [0-1], the weight, and a posible
# reduction for submission between duedate and overduedate)
# what is the final point value? This function will round to
@@ -1181,7 +1186,7 @@
'<li>'.&mt('Group(s)').": $group_display</li>\n".
'<li>'.&mt('Status').": $status_display</li>\n".
'</ul>';
- my ($classlist,undef,$fullname) = &getclasslist($sections,'1',$groups,'','','',$chosen);
+ my ($classlist,undef,$fullname) = &getclasslist($sections,'1',$groups,'','','','',$chosen);
if (keys(%$fullname)) {
$newcommand = 'passbackscores';
$result .= &build_section_inputs().
@@ -1291,7 +1296,7 @@
}
my ($sections,$groups,$group_display,$disabled) = §ions_and_groups();
my ($classlist,undef,$fullname,$pbinfo) =
- &getclasslist($sections,'1',$groups,'','','',$chosen,\%possibles);
+ &getclasslist($sections,'1',$groups,'','','','',$chosen,\%possibles);
if ((ref($classlist) eq 'HASH') && (ref($pbinfo) eq 'HASH')) {
my %passback = %{$pbinfo};
my (%tosend,%remotenotok,%scorenotok,%zeroposs,%nopbinfo);
@@ -1958,6 +1963,409 @@
return %pbc;
}
+sub initialgrace {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my %symbpartgrace = &Apache::lonnet::dump('nohist_grace',$cdom,$cnum,"^$symb\0");
+ my ($result,$newcommand,$submittext);
+ $result = '<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
+ '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
+ if (keys(%symbpartgrace)) {
+ my %latestudents = &Apache::lonnet::dump('nohist_latepenalty',$cdom,$cnum);
+ if (keys(%latestudents)) {
+ $submittext = &mt('Next').' →';
+ $newcommand = 'displaygrace';
+ $result .= &selectfield(0)."\n";
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'gradingmenu';
+ $result .= '<span class="LC_warning">'.
+ &mt('No fractional credit for late submissions applies to students in this '.lc($crstype).'.').
+ '</span>';
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'gradingmenu';
+ $result .= '<span class="LC_warning">'.
+ &mt('No submissions made for this problem during grace period after due date.').
+ '</span>';
+ }
+ $result .= '<input type="hidden" name="command" value="'.$newcommand.'" />'."\n".
+ '<div>'."\n".
+ '<input type="submit" value="'.$submittext.'" />'."\n".
+ '</div>'."\n".
+ '</form>'."\n";
+ return $result;
+}
+
+sub displaygrace {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my ($result,$newcommand,$submittext,$readonly,$disabled,%current);
+ my $ctr = 0;
+ my $updateable = 0;
+ unless ($perm{'mgr'}) {
+ $disabled = ' disabled="disabled"';
+ $readonly = 1;
+ }
+ my @statuses = &Apache::loncommon::get_env_multiple('form.Status');
+ my $stu_status = join(':', at statuses);
+ $result .= '<form action="/adm/grades" method="post" name="graceusers">'."\n".
+ '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
+ '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
+ my %symbpartgrace = &Apache::lonnet::dump('nohist_grace',$cdom,$cnum,"^$symb\0");
+ if (keys(%symbpartgrace)) {
+ my %latestudents = &Apache::lonnet::dump('nohist_latepenalty',$cdom,$cnum);
+ if (keys(%latestudents)) {
+ my %partlist;
+ foreach my $key (keys(%symbpartgrace)) {
+ my ($part) = ($key =~ /^\Q$symb\E\0(.+)$/);
+ $partlist{$part} = 1;
+ }
+ my ($sections,$groups,$group_display) = §ions_and_groups();
+ my $section_display = join(' ',@{$sections});
+ my $status_display;
+ if ((grep(/^Any$/, at statuses)) ||
+ (@statuses == 3)) {
+ $status_display = &mt('Any');
+ } else {
+ $status_display = join(' '.&mt('or').' ',map { &mt($_); } @statuses);
+ }
+ $result .= '<p>'.&mt('Student(s) with fractional credit for late submission(s) who also satisfy:').
+ '<ul>'.
+ '<li>'.&mt('Section(s)').": $section_display</li>\n".
+ '<li>'.&mt('Group(s)').": $group_display</li>\n".
+ '<li>'.&mt('Status').": $status_display</li>\n".
+ '</ul>';
+ my ($classlist,undef,$fullname) = &getclasslist($sections,'1',$groups,'','','',1,'',\%latestudents);
+ if ((ref($classlist) eq 'HASH') && (ref($fullname) eq 'HASH') &&
+ (keys(%{$fullname}))) {
+ my $preamble = &build_section_inputs().
+ &checkselect_js('graceusers').
+ '<p><br />'.
+ &mt("To update fractional credit applied to late submission(s), check box(es) for each part for student(s) for whom 'current' and 'new' differ, then push 'Update'.").
+ '</p>'."\n".
+ &check_script('graceusers','stuinfo')."\n".
+ '<input type="button" '.
+ 'onclick="javascript:checkSelect(this.form.stuinfo);" '.
+ 'value="'.&mt('Update').'"'.$disabled.' /><br />'."\n".
+ &check_buttons($disabled)."\n";
+ my $table = &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ '<th>'.&mt('No.').'</th>'.
+ '<th>'.&nameUserString('header')."</th>\n";
+ foreach my $part (sort(keys(%partlist))) {
+ $table.= '<th>'.&mt('Current post-due fraction').'<br />'.&mt('Part').
+ ': '.$part.'</th>'."\n".
+ '<th>'.&mt('New post-due fraction').'<br />'.&mt('Part').
+ ': '.$part.'</th>'."\n".
+ '<th>'.&mt('Update to new').'<br />'.&mt('Part').
+ ': '.$part.'</th>'."\n";
+ }
+ $table.=&Apache::loncommon::end_data_table_header_row();
+ foreach my $student (sort
+ {
+ if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
+ return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
+ }
+ return $a cmp $b;
+ }
+ (keys(%$fullname))) {
+ $ctr ++;
+ my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
+ my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
+ my $udom = $classlist->{$student}->[&Apache::loncoursedata::CL_SDOM()];
+ my $uname = $classlist->{$student}->[&Apache::loncoursedata::CL_SNAME()];
+ my %record =
+ &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
+ $table.= &Apache::loncommon::start_data_table_row().
+ '<td>'.$ctr.'</td><td>'.
+ &nameUserString(undef,$$fullname{$student},$uname,$udom).
+ ' '.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
+ foreach my $part (sort(keys(%partlist))) {
+ if (exists($record{'resource.'.$part.'.latefrac'})) {
+ my $currfrac = $record{'resource.'.$part.'.latefrac'};
+ my $grace = &Apache::lonnet::EXT("resource.$part.grace",$symb,
+ $udom,$uname);
+ my $newfrac;
+ if ($grace) {
+ $newfrac =
+ &compute_latefrac($part,$grace,$symb,$udom,$uname,\%record);
+ }
+ $table.='<td>'.$currfrac.'</td>'.
+ '<td>'.$newfrac.'</td>';
+ if (($newfrac eq '') || ($newfrac eq $currfrac)) {
+ $table.='<td> </td>';
+ } else {
+ my $value = $uname.':'.$udom.':'.$part.':'.$newfrac.':'.
+ &escape($fullname->{$student}).':::SECTION'.$section;
+ $table.='<td><input type="checkbox" name="stuinfo" value="'.
+ &HTML::Entities::encode($value,'\'"<>&').'"'.$disabled.' /></td>'."\n";
+ $updateable ++;
+ }
+ } else {
+ $table.='<td colspan="3"> </td>';
+ }
+ }
+ $table.=&Apache::loncommon::end_data_table_row();
+ }
+ $table.=&Apache::loncommon::end_data_table();
+ if ($updateable) {
+ $newcommand = 'updategrace';
+ $result .= $preamble.$table.
+ '<input type="hidden" value="'.$updateable.'" name="totalboxes" />'.
+ '<input type="button" '.
+ 'onclick="javascript:checkSelect(this.form.stuinfo,this.form.totalboxes);" '.
+ 'value="'.&mt('Update').'"'.$disabled.' />'."\n";
+ } else {
+ $result .= $table;
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'initialgrace';
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'initialgrace';
+ $result .= '<span class="LC_warning">'.
+ &mt('No students match the selection criteria').
+ '</span>';
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'initialgrace';
+ $result .= '<span class="LC_warning">'.
+ &mt('No fractional credit for late submissions applies to students in this '.lc($crstype).'.').
+ '</span>';
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'initialgrace';
+ $result .= '<span class="LC_warning">'.
+ &mt('No submissions made for this problem during grace period after due date.').
+ '</span>';
+ }
+ $result .= '<input type="hidden" name="command" value="'.$newcommand.'" />'."\n";
+ if (!$ctr || !$updateable) {
+ $result .= '<div>'."\n".
+ '<input type="submit" value="'.$submittext.'" />'."\n".
+ '</div>'."\n";
+ }
+ $result .= '</form>'."\n";
+ return $result;
+}
+
+sub updategrace {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my @statuses = &Apache::loncommon::get_env_multiple('form.Status');
+ my $stu_status = join(':', at statuses);
+ my ($result,$newcommand,$submittext);
+ $result = '<form action="/adm/grades" method="post" name="graceusers">'."\n".
+ '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
+ '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
+ if ($perm{'mgr'}) {
+ my %symbpartgrace = &Apache::lonnet::dump('nohist_grace',$cdom,$cnum,"^$symb\0");
+ if (keys(%symbpartgrace)) {
+ my %latestudents = &Apache::lonnet::dump('nohist_latepenalty',$cdom,$cnum);
+ if (keys(%latestudents)) {
+ my @poss_students = &Apache::loncommon::get_env_multiple('form.stuinfo');
+ if (@poss_students) {
+ my (%possibles,%parts_with_updates,%failed,%fullnames,%sections);
+ foreach my $item (@poss_students) {
+ my ($stuname,$studom,$part,$newfrac,$fullname,$rest) = split(/:/,$item,6);
+ $fullnames{$stuname.':'.$studom} = &unescape($fullname);
+ ($sections{$stuname.':'.$studom}) = ($rest =~ /\Q:::SECTION\E(.*)$/);
+ if (($symbpartgrace{"$symb\0$part"}) && ($latestudents{$stuname.':'.$studom})) {
+ $possibles{$stuname.':'.$studom}{$part} = $newfrac;
+ $parts_with_updates{$part} = 1;
+ }
+ }
+ if (keys(%possibles)) {
+ my ($tableheader,$headershown);
+ $tableheader = &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ '<th>'.&nameUserString('header')."</th>\n";
+ foreach my $part (sort(keys(%parts_with_updates))) {
+ $tableheader.= '<th>'.&mt('New post-due fraction').'<br />'.&mt('Part').
+ ': '.$part.'</th>'."\n";
+ }
+ $tableheader.=&Apache::loncommon::end_data_table_header_row();
+ my %needpb = &passbacks_for_symb($cdom,$cnum,$symb);
+ my ($partlist,$passback,%skip_passback,%pbsave);
+ if (keys(%needpb)) {
+ my $res_error;
+ ($partlist) = &response_type($symb,\$res_error);
+ $passback = 1;
+ }
+ foreach my $user (sort
+ {
+ if (lc($fullnames{$a}) ne lc($fullnames{$b})) {
+ return (lc($fullnames{$a}) cmp lc($fullnames{$b}));
+ }
+ return $a cmp $b;
+ } (keys(%fullnames))) {
+ if ((exists($possibles{$user})) && (ref($possibles{$user}) eq 'HASH')) {
+ my ($stuname,$studom) = split(/:/,$user);
+ my %grades=();
+ foreach my $part (sort(keys(%{$possibles{$user}}))) {
+ $grades{"resource.$part.latefrac"} = $possibles{$user}{$part};
+ $grades{"resource.$part.regrader"} = "$env{'user.name'}:$env{'user.domain'}";
+ }
+ if (keys(%grades)) {
+ my $res = &Apache::lonnet::cstore(\%grades,$symb,
+ $env{'request.course.id'},
+ $studom,$stuname);
+ if ($res eq 'ok') {
+ my %updatedparts;
+ map { $updatedparts{$_} = 1; } keys(%{$possibles{$user}});
+ unless ($headershown) {
+ $result .= $tableheader;
+ $headershown = 1;
+ }
+ $result .= &Apache::loncommon::start_data_table_row().
+ '<td>'.&nameUserString('',$fullnames{$user},$stuname,$studom).
+ '</td>';
+ foreach my $part (sort(keys(%parts_with_updates))) {
+ if (exists($updatedparts{$part})) {
+ $result .= '<td>'.$possibles{$user}{$part}.'</td>';
+ } else {
+ $result .= '<td> </td>';
+ }
+ }
+ $result .= &Apache::loncommon::end_data_table_row();
+ if ($passback) {
+ my %record =
+ &Apache::lonnet::restore($symb,$env{'request.course.id'},
+ $studom,$stuname);
+ if (ref($partlist) eq 'ARRAY') {
+ my (%weights,%awardeds,%excuseds,%latefracs);
+ foreach my $part (@{$partlist}) {
+ $latefracs{$symb}{$part} = $record{"resource.$part.latefrac"};
+ $weights{$symb}{$part} =
+ &Apache::lonnet::EXT('resource.'.$part.'.weight',
+ $symb,$studom,$stuname);
+ if ($record{"resource.$part.solved"} =~/^excused/) {
+ $excuseds{$symb}{$part} = 1;
+ } else {
+ $excuseds{$symb}{$part} = '';
+ }
+ $awardeds{$symb}{$part} = $record{"resource.$part.awarded"};
+ }
+ my $stusec = $sections{$user};
+ &process_passbacks('updategrace',[$symb],$cdom,$cnum,$studom,$stuname,
+ $stusec,\%weights,\%awardeds,\%excuseds,\%latefracs,
+ \%needpb,\%skip_passback,\%pbsave);
+ }
+ }
+ } else {
+ $failed{$user} = 1;
+ }
+ }
+ }
+ }
+ $result .= &Apache::loncommon::end_data_table();
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'displaygrace';
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'displaygrace';
+ $result .= '<span class="LC_warning">'.
+ &mt('Student(s) or part(s) selected for updates to fractional credit do not have existing fractional credit for submission(s) between due date and end of grace period.').
+ '</span>';
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'displaygrace';
+ $result .= '<span class="LC_warning">'.
+ &mt('No students selected for updates to fractional credit').
+ '</span>';
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'initialgrace';
+ $result .= '<span class="LC_warning">'.
+ &mt('No fractional credit for late submissions applies to students in this '.lc($crstype).'.').
+ '</span>';
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'initialgrace';
+ $result .= '<span class="LC_warning">'.
+ &mt('No submissions made for this problem during grace period after due date.').
+ '</span>';
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'displaygrace';
+ $result .= '<span class="LC_warning">'.
+ &mt('You do not have permission to update partial credit for late submission.').
+ '</span>';
+ }
+ $result .= '<input type="hidden" name="command" value="'.$newcommand.'" />'."\n".
+ '<div>'."\n".
+ '<input type="submit" value="'.$submittext.'" />'."\n".
+ '</div>'."\n".
+ '</form>'."\n";
+ return $result;
+}
+
+sub compute_latefrac {
+ my ($part,$grace,$symb,$udom,$uname,$record) = @_;
+ my $newlatefrac;
+ if (($grace) && (ref($record) eq 'HASH')) {
+ my ($partoverdue,$partdue,$grace_end,$offset);
+ $partdue = $record->{'resource.'.$part.'.duedate'},
+ $grace_end = (split(/,/,$grace))[-1];
+ ($offset) = split(/:/,$grace_end,2);
+ if ($offset > 0) {
+ $partoverdue = $offset + $partdue;
+ }
+ if ($record->{'version'}) {
+ my $version;
+ my $lastsubmittime = 0;
+ my $lastduedate = 0;
+ my $lastresettime;
+ for ($version=1;$version<=$record->{'version'};$version++) {
+ if ((exists($record->{$version.':resource.'.$part.'.pastdue'})) &&
+ (exists($record->{$version.':resource.'.$part.'.duedate'}))) {
+ my $submittime = $record->{$version.':resource.'.$part.'.pastdue'}
+ + $record->{$version.':resource.'.$part.'.duedate'};
+ if ($submittime > $lastsubmittime) {
+ $lastsubmittime = $submittime;
+ $lastduedate = $record->{$version.':resource.'.$part.'.duedate'};
+ }
+ }
+# foreach my $key (sort(split(/\:/,
+# $record->{$version.':keys'}))) {
+# if ($key =~ /\Q$part\E\.[^.]+\.portfiles$/) {
+# if (($record->{$version.':'.$key} ne '') &&
+# ($record->{$version.':'.$key} !~ /\.\d+\.\w+$/)) {
+# if ($record->{$version.':timestamp'} > $lastsubmittime) {
+# $lastsubmittime = $record->{$version.':timestamp'};
+# }
+# }
+# } elsif ($key =~ /\Q$part\E\.[^.]+\.submission$/) {
+# if ($record->{$version.':'.$key} ne '') {
+# if ($record->{$version.':timestamp'} > $lastsubmittime) {
+# $lastsubmittime = $record->{$version.':timestamp'};
+# }
+# }
+# }
+# }
+ }
+ $newlatefrac =
+ &Apache::lonhomework::partial_credit_overdue($part,$lastsubmittime,
+ $lastduedate,$symb,$udom,$uname);
+ }
+ }
+ return $newlatefrac;
+}
+
#--- This is called by a number of programs.
#--- Called from the Grading Menu - View/Grade an individual student
#--- Also called directly when one clicks on the subm button
@@ -2291,6 +2699,11 @@
'multiple' => 'Please select a student or group of students before pushing the Save Scores button.',
'single' => 'Please select the student before pushing the Save Scores button.',
);
+ } elsif ($formname eq 'passbackusers') {
+ %js_lt = &Apache::lonlocal::texthash (
+ 'multiple' => 'Please select a student or group of students before pushing the Update button.',
+ 'single' => 'Please select the student before pushing the Update button.',
+ );
} else {
%js_lt = &Apache::lonlocal::texthash (
'multiple' => 'Please select a student or group of students before clicking on the Next button.',
@@ -2335,8 +2748,8 @@
for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
ele = document.forms.'.$form.'.elements[i];
if (ele.name == "'.$type.'") {
- document.forms.'.$form.'.elements[i].checked=true;
- }
+ document.forms.'.$form.'.elements[i].checked=true;
+ }
}
}
@@ -2344,8 +2757,7 @@
for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
ele = document.forms.'.$form.'.elements[i];
string = document.forms.'.$form.'.chksec.value;
- if
- (ele.value.indexOf(":::SECTION"+string)>0) {
+ if (ele.value.indexOf(":::SECTION"+string)>0) {
document.forms.'.$form.'.elements[i].checked=true;
}
}
@@ -2366,10 +2778,11 @@
}
sub check_buttons {
- my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
- $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" /> ';
- $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
- $buttons.='<input type="text" size="5" name="chksec" /> ';
+ my ($disabled) = @_;
+ my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'"'.$disabled.' />';
+ $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'"'.$disabled.' /> ';
+ $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'"'.$disabled.' />';
+ $buttons.='<input type="text" size="5" name="chksec"'.$disabled.' /> ';
return $buttons;
}
@@ -2992,6 +3405,8 @@
$wgt = ($wgt > 0 ? $wgt : '1');
my $latefrac = $record->{'resource.'.$partid.'.latefrac'};
my $endgrace = $record->{'resource.'.$partid.'.endgrace'};
+ my $pastdue = $record->{'resource.'.$partid.'.pastdue'};
+ my $duedate = $record->{'resource.'.$partid.'.duedate'};
my $score = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
'' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
my $data_WGT='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
@@ -3031,6 +3446,8 @@
$line.='<td>'.$latefrac.
'<input type="hidden" name="latefrac'.$counter.'_'.$partid.'" value="'.$latefrac.'" />'."\n".
'<input type="hidden" name="endgrace'.$counter.'_'.$partid.'" value="'.$endgrace.'" />'."\n".
+ '<input type="hidden" name="pastdue'.$counter.'_'.$partid.'" value="'.$pastdue.'" />'."\n".
+ '<input type="hidden" name="duedate'.$counter.'_'.$partid.'" value="'.$duedate.'" />'."\n".
'</td>';
$colspan ++;
}
@@ -4541,7 +4958,7 @@
if ((ref($needpb) eq 'HASH') && (keys(%{$needpb}))) {
$poss_pb = 1;
}
- my (%weights,%awardeds,%excuseds,%latefracs,$cblatefrac,$cbendgrace);
+ my (%weights,%awardeds,%excuseds,%latefracs,$cblatefrac,$cbendgrace,$cbpastdue,$cbduedate);
my @parts = split(/:/,$env{'form.partlist'.$newflg});
foreach my $new_part (@parts) {
#collaborator ($submitter may vary for different parts)
@@ -4558,6 +4975,8 @@
if ($submitter) {
$cblatefrac = $env{'form.latefrac'.$newflg.'_'.$new_part};
$cbendgrace = $env{'form.endgrace'.$newflg.'_'.$new_part};
+ $cbpastdue = $env{'form.pastdue'.$newflg.'_'.$new_part};
+ $cbduedate = $env{'form.duedate'.$newflg.'_'.$new_part};
$latefracs{$symb}{$new_part} = $env{'form.latefrac'.$newflg.'_'.$new_part};
} else {
$latefracs{$symb}{$new_part} = $record{'resource.'.$new_part.'.latefrac'};
@@ -4632,17 +5051,22 @@
$newrecord{$reckey} = 'correct_by_override';
}
}
- if ($submitter &&
- ($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
- $newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
- }
- if ($submitter &&
- ($record{'resource.'.$new_part.'.latefrac'} ne $cblatefrac)) {
- $newrecord{'resource.'.$new_part.'.latefrac'} = $cblatefrac;
- }
- if ($submitter &&
- ($record{'resource.'.$new_part.'.endgrace'} ne $cbendgrace)) {
- $newrecord{'resource.'.$new_part.'.endgrace'} = $cbendgrace;
+ if ($submitter) {
+ if ($record{'resource.'.$new_part.'.submitted_by'} ne $submitter) {
+ $newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
+ }
+ if ($record{'resource.'.$new_part.'.latefrac'} ne $cblatefrac) {
+ $newrecord{'resource.'.$new_part.'.latefrac'} = $cblatefrac;
+ }
+ if ($record{'resource.'.$new_part.'.endgrace'} ne $cbendgrace) {
+ $newrecord{'resource.'.$new_part.'.endgrace'} = $cbendgrace;
+ }
+ if ($record{'resource.'.$new_part.'.pastdue'} ne $cbpastdue) {
+ $newrecord{'resource.'.$new_part.'.pastdue'} = $cbpastdue;
+ }
+ if ($record{'resource.'.$new_part.'.duedate'} ne $cbduedate) {
+ $newrecord{'resource.'.$new_part.'.duedate'} = $cbduedate;
+ }
}
$newrecord{'resource.'.$new_part.'.regrader'}=
"$env{'user.name'}:$env{'user.domain'}";
@@ -5692,6 +6116,8 @@
if ($latefrac) {
$newrecord{'resource.'.$_.'.latefrac'} = '';
$newrecord{'resource.'.$_.'.endgrace'} = '';
+ $newrecord{'resource.'.$_.'.pastdue'} = '';
+ $newrecord{'resource.'.$_.'.duedate'} = '';
}
$updateflag = 1;
if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
@@ -7030,6 +7456,8 @@
if ($env{'form.latefrac'.$question.'_'.$partid} ne '') {
$newrecord{'resource.'.$partid.'.latefrac'} = '';
$newrecord{'resource.'.$partid.'.endgrace'} = '';
+ $newrecord{'resource.'.$partid.'.pastdue'} = '';
+ $newrecord{'resource.'.$partid.'.duedate'} = '';
$latefracs{$symbx}{$partid} = '';
}
$changeflag++;
@@ -11881,6 +12309,9 @@
$fields{'command'}='downloadfilesselect';
my $url1e=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+ $fields{'command'} = 'initialgrace';
+ my $url1f = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
$fields{'command'} = 'csvform';
my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
@@ -11935,8 +12366,15 @@
permission => $permissions{'either'},
icon => 'download_sub.png',
linktitle => 'Download all students submissions.'
- }]},
- { categorytitle=>'Automated Grading',
+ },
+ { linktext => 'Fractional credit for late submission',
+ url => $url1f,
+ permission => $permissions{'either'},
+ icon => 'updategrace.png',
+ linktitle => 'Display fractional credit for late submission with possible update if grace parameter has been changed since submission',
+ }
+ ]},
+ { categorytitle=>'Automated Grading',
items =>[
{ linktext => 'Upload Scores',
@@ -13252,6 +13690,22 @@
{href=>&href_symb_cmd($symb,'passbacknames').'&Status='.$stu_status.'&passback='.$chosen, text=>'Select Users'},
{href=>'', text=>'Execute Passback'}],undef,1);
$request->print(&do_passback($request,$symb));
+ } elsif ($command eq 'initialgrace') {
+ &startpage($request,$symb,[{href=>'', text=>'Types of User'}]);
+ $request->print(&initialgrace($request,$symb));
+ } elsif ($command eq 'displaygrace') {
+ &startpage($request,$symb,
+ [{href=>&href_symb_cmd($symb,'initialgrace'), text=>'Types of User'},
+ {href=>'', text=>'Display Post-Due'}]);
+ $request->print(&displaygrace($request,$symb));
+ } elsif ($command eq 'updategrace') {
+ my @statuses = &Apache::loncommon::get_env_multiple('form.Status');
+ my $stu_status = join(':', at statuses);
+ &startpage($request,$symb,
+ [{href=>&href_symb_cmd($symb,'initialgrace'), text=>'Types of User'},
+ {href=>&href_symb_cmd($symb,'displaygrace').'&Status='.$stu_status, text=>'Display Post-Due'},
+ {href=>'', text=>'Update Result'}]);
+ $request->print(&updategrace($request,$symb));
} elsif ($command) {
&startpage($request,$symb,[{href=>'', text=>'Access denied'}]);
$request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
Index: loncom/interface/lonnavmaps.pm
diff -u loncom/interface/lonnavmaps.pm:1.580 loncom/interface/lonnavmaps.pm:1.581
--- loncom/interface/lonnavmaps.pm:1.580 Wed Aug 13 00:12:12 2025
+++ loncom/interface/lonnavmaps.pm Thu Aug 21 16:21:42 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.580 2025/08/13 00:12:12 raeburn Exp $
+# $Id: lonnavmaps.pm,v 1.581 2025/08/21 16:21:42 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -5328,14 +5328,19 @@
return $overduedate;
}
sub partial_credit_overdue {
- my ($self,$part) = @_;
+ my ($self,$part,$duedate,$submtime) = @_;
my $reduction;
- my $duedate = $self->parmval("duedate", $part);
+ if ($duedate eq '') {
+ $duedate = $self->parmval("duedate", $part);
+ }
if ($duedate) {
my @interval = $self->parmval("interval", $part);
my $grace = $self->parmval("grace",$part);
if (($grace) && ($interval[0] !~ /^\d+/)) {
- my $lateness = time - $duedate;
+ if ($submtime eq '') {
+ $submtime = time;
+ }
+ my $lateness = $submtime - $duedate;
if ($lateness > 0) {
my ($start,$end,$startfrac,$endfrac,$usegrad);
$start = 0;
Index: doc/loncapafiles/loncapafiles.lpml
diff -u doc/loncapafiles/loncapafiles.lpml:1.1078 doc/loncapafiles/loncapafiles.lpml:1.1079
--- doc/loncapafiles/loncapafiles.lpml:1.1078 Thu Jul 31 15:15:38 2025
+++ doc/loncapafiles/loncapafiles.lpml Thu Aug 21 16:21:42 2025
@@ -2,7 +2,7 @@
"http://lpml.sourceforge.net/DTD/lpml.dtd">
<!-- loncapafiles.lpml -->
-<!-- $Id: loncapafiles.lpml,v 1.1078 2025/07/31 15:15:38 raeburn Exp $ -->
+<!-- $Id: loncapafiles.lpml,v 1.1079 2025/08/21 16:21:42 raeburn Exp $ -->
<!--
@@ -8672,6 +8672,7 @@
timezone.png;
trck-22x22.png;
ungrade_sub.png;
+updategrace.png;
uplcrs.png;
uploadscores.png;
verify.png;
More information about the LON-CAPA-cvs
mailing list