File:  [LON-CAPA] / loncom / interface / statistics / lonstudentassessment.pm
Revision 1.31: download - view: text, annotated - select for diffs
Mon Mar 3 19:28:29 2003 UTC (21 years, 2 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
Big Changes:
   Started adding different output options.
   No longer begins computing before the user has made selections.

The code is being simplified to some degree and obfuscated in other ways.

Each output type has three subroutines associated with it: initialize,
outputstudent, and finish.  Function pointers are used (defaulting the output
mode to 'html').  Currently excel and csv output both result in
"Not Implemented".

Renamed &CreateTableHeadings to &html_initialize, &ChartOutputStudent to
&html_outputstudent, and added &html_finish.  Each of these routines was
changed to make it responsible for *all* html output of the chart, other than
the selection form.

Still remaining to be filled in are the routines for csv and excel output.

# The LearningOnline Network with CAPA
#
# $Id: lonstudentassessment.pm,v 1.31 2003/03/03 19:28:29 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#
# (Navigate problems for statistical reports
#
#######################################################
#######################################################

=pod

=head1 NAME

lonstudentassessment

=head1 SYNOPSIS

Presents assessment data about a student or a group of students.

=head1 Subroutines

=over 4 

=cut

#######################################################
#######################################################

package Apache::lonstudentassessment;

use strict;
use Apache::lonstatistics;
use Apache::lonhtmlcommon;
use Apache::loncoursedata;
use Apache::lonnet; # for logging porpoises
use Spreadsheet::WriteExcel;

#######################################################
#######################################################
=pod

=item Package Variables

=over 4

=item $Statistics Hash ref to store student data.  Indexed by symb,
      contains hashes with keys 'score' and 'max'.

=cut

#######################################################
#######################################################

my $Statistics;

#######################################################
#######################################################

=pod

=item $show_links 'yes' or 'no' for linking to student performance data

=item $output_mode 'html', 'excel', or 'csv' for output mode

=item $show 'all' or 'totals' determines how much data is output

=cut

#######################################################
#######################################################
my $show_links;
my $output_mode;
my $show;

#######################################################
#######################################################
# End of package variable declarations

=pod

=back

=cut

#######################################################
#######################################################

=pod

=item &BuildStudentAssessmentPage()

Inputs: 

=over 4

=item $r Apache Request

=item $c Apache Connection 

=back

=cut

#######################################################
#######################################################
sub BuildStudentAssessmentPage {
    my ($r,$c)=@_;
    undef($Statistics);
    #
    # Print out the HTML headers for the interface
    #    This also parses the output mode selector
    #    This step must always be done.
    $r->print(&CreateInterface());
    $r->print('<input type="hidden" name="notfirstrun" value="true" />');
    $r->rflush();
    if (! exists($ENV{'form.notfirstrun'})) {
        $r->print(<<ENDMSG);
<p>
<font size="+1">
Please make your selections in the boxes above and hit 
the button marked &quot;Update&nbsp;Display&quot;.
</font>
</p>
ENDMSG
        return;
    }
    #
    #
    my $initialize     = \&html_initialize;
    my $output_student = \&html_outputstudent;
    my $finish         = \&html_finish;
    #
    if ($output_mode eq 'excel') {
        $initialize     = \&excel_initialize;
        $output_student = \&excel_outputstudent;
        $finish         = \&excel_finish;
    } elsif ($output_mode eq 'csv') {
        $initialize     = \&csv_initialize;
        $output_student = \&csv_outputstudent;
        $finish         = \&csv_finish;
    }
    #
    if($c->aborted()) {  return ; }
    #
    # Call the initialize routine selected above
    $initialize->($r);
    foreach my $student (@Apache::lonstatistics::Students) {
        if($c->aborted()) { 
            $finish->($r);
            return ; 
        }
        # Call the output_student routine selected above
        $output_student->($r,$student);
    }
    # Call the "finish" routine selected above
    $finish->($r);
    #
    return;
}

#######################################################
#######################################################

sub get_student_fields_to_show {
    my @to_show = @Apache::lonstatistics::SelectedStudentData;
    foreach (@to_show) {
        if ($_ eq 'all') {
            @to_show = @Apache::lonstatistics::StudentDataOrder;
            last;
        }
    }
    return @to_show;
}

sub get_sequences_to_show {
    my @Sequences;
    foreach my $map_symb (@Apache::lonstatistics::SelectedMaps) {
        foreach my $sequence (@Apache::lonstatistics::Sequences) {
            next if ($sequence->{'symb'} ne $map_symb && $map_symb ne 'all');
            next if ($sequence->{'num_assess'} < 1);
            push (@Sequences,$sequence);
        }
    }
    return @Sequences;
}


#######################################################
#######################################################

=pod

=item &CreateInterface()

Called by &BuildStudentAssessmentPage to create the top part of the
page which displays the chart.

Inputs: None

Returns:  A string containing the HTML for the headers and top table for 
the chart page.

=cut

#######################################################
#######################################################
sub CreateInterface {
    my $Str = '';
#    $Str .= &CreateLegend();
    $Str .= '<table cellspacing="5">'."\n";
    $Str .= '<tr>';
    $Str .= '<td align="center"><b>Sections</b></td>';
    $Str .= '<td align="center"><b>Student Data</b></td>';
    $Str .= '<td align="center"><b>Sequences and Folders</b></td>';
    $Str .= '<td align="center"><b>Output Format</b></td>';
    $Str .= '</tr>'."\n";
    #
    $Str .= '<tr><td align="center">'."\n";
    $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
    $Str .= '</td><td align="center">';
    my $only_seq_with_assessments = sub { 
        my $s=shift;
        if ($s->{'num_assess'} < 1) { 
            return 0;
        } else { 
            return 1;
        }
    };
    $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
                                                      5,undef);
    $Str .= '</td><td>'."\n";
    $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
                                              $only_seq_with_assessments);
    $Str .= '</td><td>'."\n";
    $Str .= &CreateAndParseOutputSelector();
    $Str .= '</td></tr>'."\n";
    $Str .= '</table>'."\n";
    return $Str;
}

#######################################################
#######################################################

=pod

=item &CreateAndParseOutputSelector()

=cut

#######################################################
#######################################################
sub CreateAndParseOutputSelector {
    my $Str = '';
    my $elementname = 'outputmode';
    #
    # Format for output options is 'mode, restrictions';
    my @Options = ('html, with links','html, without links',
                   'html, totals only','excel, totals only',
                   'csv, totals only','csv, everything');
    my $selected = 'html, with links';
    if (exists($ENV{'form.'.$elementname})) {
        if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
            $selected = $ENV{'form.'.$elementname}->[0];
        } else {
            $selected = $ENV{'form.'.$elementname};
        }
    }
    #
    # Set package variables describing output mode
    $show_links  = 'no';
    $output_mode = 'html';
    $show        = 'all';
    my ($mode,$restriction) = split(',',$selected);
    $restriction =~ s/^\s*//;
    if ($mode =~ /^(html|excel|csv)$/) {
        $output_mode = $mode;
    } else {
        $output_mode = 'html';
    }
    if ($restriction eq 'with links') {
        $show_links = 'yes';
    } else {
        $show_links = 'no';
    }
    if ($restriction eq 'totals only') {
        $show = 'totals';
    } else {
        $show = 'everything';
    }
    #
    # Build the form element
    $Str = qq/<select size="5" name="$elementname">/;
    foreach my $option (@Options) {
        $Str .= qq/\n    <option value="$option"/;
        $Str .= " selected " if ($option eq $selected);
        $Str .= ">$option<\/option>";
    }
    $Str .= "\n</select>";
    return $Str;
}

#######################################################
#######################################################

=pod

=head2 HTML output routines

=item &html_initialize($r)

Create labels for the columns of student data to show.

=item &html_outputstudent($r,$student)

Return a line of the chart for a student.

=item &html_finish($r)

=cut

#######################################################
#######################################################
{
    my $padding;
    my $count;

sub html_initialize {
    my ($r) = @_;
    #
    $padding = ' 'x3;
    $count = 1;
    #
    my $Str = "<pre>\n";
    # First, the @StudentData fields need to be listed
    my @to_show = &get_student_fields_to_show();
    foreach my $field (@to_show) {
        my $title=$Apache::lonstatistics::StudentData{$field}->{'title'};
        my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'};
        my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
        $Str .= $title.' 'x($width-$base).$padding;
    }
    # Now the selected sequences need to be listed
    foreach my $sequence (&get_sequences_to_show) {
        my $title = $sequence->{'title'};
        my $base  = $sequence->{'base_width'};
        my $width = $sequence->{'width'};
        $Str .= $title.' 'x($width-$base).$padding;
    }
    $Str .= "total (of shown problems)</pre>\n";
    $Str .= "<pre>";
    $r->print($Str);
    $r->rflush();
    return;
}

sub html_outputstudent {
    my ($r,$student) = @_;
    my $Str = '';
    # First, the @StudentData fields need to be listed
    my @to_show = &get_student_fields_to_show();
    foreach my $field (@to_show) {
        my $title=$student->{$field};
        my $base = length($title);
        my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
        $Str .= $title.' 'x($width-$base).$padding;
    }
    # Get ALL the students data
    my %StudentsData;
    my @tmp = &Apache::loncoursedata::get_current_state
        ($student->{'username'},$student->{'domain'},undef,
         $ENV{'request.course.id'});
    if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
        %StudentsData = @tmp;
    }
    if (scalar(@tmp) < 1) {
        $Str .= '<font color="blue">No Course Data</font>'."\n";
        $r->print($Str);
        $r->rflush();
        return;
    }
    #
    # By sequence build up the data
    my $studentstats;
    my $PerformanceStr = '';
    foreach my $seq (&get_sequences_to_show) {
        my ($performance,$score,$seq_max) =
            &StudentPerformanceOnSequence($student,\%StudentsData,
                                          $seq,$show_links);
        my $ratio = $score.'/'.$seq_max;
        #
        if ($show eq 'totals') {
            $performance = ' 'x(length($seq_max)-length($score)).$ratio;
            $performance .= ' 'x($seq->{'width'}-length($performance));
        } else {
            # Pad with extra spaces
            $performance .= ' 'x($seq->{'width'}-$seq_max-
                                 length($ratio)
                                 ).$ratio;
        }
        #
        $Str .= $performance.$padding;
        #
        $studentstats->{$seq->{'symb'}}->{'score'}= $score;
        $studentstats->{$seq->{'symb'}}->{'max'}  = $seq_max;
    }
    #
    # Total it up and store the statistics info.
    my ($score,$max) = (0,0);
    while (my ($symb,$seq_stats) = each (%{$studentstats})) {
        $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
        $Statistics->{$symb}->{'max'}   += $seq_stats->{'max'};
        $score += $seq_stats->{'score'};
        $max   += $seq_stats->{'max'};
    }
    $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
    $Str .= " \n";
    $r->print($Str);
    #
    $count++;
    if($count % 5 == 0) {
        $r->print("</pre><pre>");
    }
    #
    $r->rflush();
    return;
}    

sub html_finish {
    my ($r) = @_;
    $r->print("</pre>\n"); 
    $r->rflush();
    return;
}

}

#######################################################
#######################################################

=pod

=head2 EXCEL subroutines

=item &excel_initialize($r)

=item &excel_outputstudent($r,$student)

=item &excel_finish($r)

=cut

#######################################################
#######################################################
{

my $excel_sheet;

sub excel_initialize {
    my ($r) = @_;
    #
    $r->print("<h1>Not implemented yet</h1>");
    return;
    my $filename = '/prtspool/'.
        $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
            time.'_'.rand(1000000000).'.xls';
    $excel_sheet = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
    if (! defined($excel_sheet)) {
        $r->log_error("Error creating excel spreadsheet $filename: $!");
        $r->print("Problems creating new Excel file.  ".
                  "This error has been logged.  ".
                  "Please alert your LON-CAPA administrator");
        return 0;
    }
    #
    # The excel spreadsheet stores temporary data in files, then put them
    # together.  If needed we should be able to disable this (memory only).
    # The temporary directory must be specified before calling 'addworksheet'.
    # File::Temp is used to determine the temporary directory.
    $excel_sheet->set_tempdir($Apache::lonnet::tmpdir);
    #
    # Determine the name to give the worksheet
#    $excel_sheet->addworksheet();

    return;
}

sub excel_outputstudent {
    my ($r,$student) = @_;
}

sub excel_finish {
    my ($r) = @_;
}

}
#######################################################
#######################################################

=pod

=head2 CSV output routines

=item &csv_initialize($r)

=item &csv_outputstudent($r,$student)

=item &csv_finish($r)

=cut

#######################################################
#######################################################
{

sub csv_initialize{
    my ($r) = @_;
    $r->print("<h1>Not implemented yet</h1>");
    return;
}

sub csv_outputstudent {
    my ($r,$student) = @_;
}

sub csv_finish {
    my ($r) = @_;
}

}

#######################################################
#######################################################

=pod

=item &StudentPerformanceOnSequence()

Inputs:

=over 4

=item $student

=item $studentdata Hash ref to all student data

=item $seq Hash ref, the sequence we are working on

=item $links if defined we will output links to each resource.

=back

=cut

#######################################################
#######################################################
sub StudentPerformanceOnSequence {
    my ($student,$studentdata,$seq,$links,$totalonly) = @_;
    $totalonly = 0 if (! defined($totalonly));
    $links = 'no' if (! defined($links));
    my $Str = '';
    my ($sum,$max) = (0,0);
    foreach my $resource (@{$seq->{'contents'}}) {
        next if ($resource->{'type'} ne 'assessment');
        my $resource_data = $studentdata->{$resource->{'symb'}};
        my $value = '';
        foreach my $partnum (@{$resource->{'parts'}}) {
            $max++;
            my $symbol = ' '; # default to space
            #
            if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
                my $status = $resource_data->{'resource.'.$partnum.'.solved'};
                if ($status eq 'correct_by_override') {
                    $symbol = '+';
                    $sum++;
                } elsif ($status eq 'incorrect_by_override') {
                    $symbol = '-';
                } elsif ($status eq 'ungraded_attempted') {
                    $symbol = '#';
                } elsif ($status eq 'incorrect_attempted')  {
                    $symbol = '.';
                } elsif ($status eq 'excused') {
                    $symbol = 'x';
                    $max--;
                } elsif ($status eq 'correct_by_student' &&
                    exists($resource_data->{'resource.'.$partnum.'.tries'})){
                    my $num = $resource_data->{'resource.'.$partnum.'.tries'};
                    if ($num > 9) {
                        $symbol = '*';
                    } elsif ($num > 0) {
                        $symbol = $num;
                    } else {
                        $symbol = ' ';
                    }
                    $sum++;
                } else {
                    $symbol = ' ';
                }
            } else {
                # Unsolved.  Did they try?
                if (exists($resource_data->{'resource.'.$partnum.'.tries'})){
                    $symbol = '.';
                } else {
                    $symbol = ' ';
                }
            }
            #
            if ($links eq 'yes' && $symbol ne ' ') {
                $symbol = '<a href="/adm/grades'.
                    '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
                        '&student='.$student->{'username'}.
                            '&domain='.$student->{'domain'}.
                                '&command=submission">'.$symbol.'</a>';
            }
            $value .= $symbol;
        }
        $Str .= $value;
    }
    return ($Str,$sum,$max);
}

#######################################################
#######################################################
sub StudentAverageTotal {
    my ($cache, $students, $sequenceKeys)=@_;
    my $Str = "\n<b>Summary Tables:</b>\n";
    my %Correct = ();
    my $ProblemsSolved = 0;
    my $TotalProblems = 0;
    my $StudentCount = 0;

    foreach my $name (@$students) {
        $StudentCount++;
        foreach my $sequence (@$sequenceKeys) {
            $Correct{$sequence} +=
	       $cache->{$name.':'.$sequence.':problemsCorrect'};
        }
	$ProblemsSolved += $cache->{$name.':problemsSolved'};
        $TotalProblems += $cache->{$name.':totalProblems'};
    }
    if ($StudentCount) { 
        $ProblemsSolved = sprintf( "%.2f", 
                             $ProblemsSolved/$StudentCount);
        $TotalProblems /= $StudentCount;
    } else {
        $ProblemsSolved = 0;
        $TotalProblems  = 0;
    }

    $Str .= '<table border=2 cellspacing="1">'."\n";
    $Str .= '<tr><td><b>Students Count</b></td><td><b>'.
            $StudentCount.'</b></td></tr>'."\n";
    $Str .= '<tr><td><b>Total Problems</b></td><td><b>'.
            $TotalProblems.'</b></td></tr>'."\n";
    $Str .= '<tr><td><b>Average Correct</b></td><td><b>'.
            $ProblemsSolved.'</b></td></tr>'."\n";
    $Str .= '</table>'."\n";

    $Str .= '<table border=2 cellspacing="1">'."\n";
    $Str .= '<tr><th>Title</th><th>Total Problems</th>'.
            '<th>Average Correct</th></tr>'."\n";
    foreach my $S(@$sequenceKeys) {
        my $title=$cache->{$S.':title'};
	#$Str .= $cache->{$S.':problems'};
	#my @problems=split(':', $cache->{$S.':problems'});
	#my $pCount=scalar @problems;
	my $pCount=MaxSeqPr($cache,@$students[0],$S);
        my $crr;
	if ($StudentCount) {
            $crr=sprintf( "%.2f", $Correct{$S}/$StudentCount );
        } else {
            $crr="0.00";
        }
        $Str .= '<tr><td>'.$title.
                '</td><td align=center>'.$pCount.
                '</td><td align=center>'.$crr.
                '</td></tr>'."\n";
    }

    $Str .= '</table>'."\n";

    return $Str;
}

#######################################################
#######################################################

=pod

=item &CreateLegend()

This function returns a formatted string containing the legend for the
chart.  The legend describes the symbols used to represent grades for
problems.

=cut

#######################################################
#######################################################
sub CreateLegend {
    my $Str = "<p><pre>".
              "   1  correct by student in 1 try\n".
              "   7  correct by student in 7 tries\n".
              "   *  correct by student in more than 9 tries\n".
	      "   +  correct by hand grading or override\n".
              "   -  incorrect by override\n".
	      "   .  incorrect attempted\n".
	      "   #  ungraded attempted\n".
              "      not attempted (blank field)\n".
	      "   x  excused".
              "</pre><p>";
    return $Str;
}

#######################################################
#######################################################

=pod 

=back

=cut

#######################################################
#######################################################

1;

__END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>