Annotation of loncom/interface/statistics/lonstudentassessment.pm, revision 1.39
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.39 ! matthew 3: # $Id: lonstudentassessment.pm,v 1.38 2003/03/07 15:18:39 matthew Exp $
1.1 stredwic 4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: # LON-CAPA is free software; you can redistribute it and/or modify
9: # it under the terms of the GNU General Public License as published by
10: # the Free Software Foundation; either version 2 of the License, or
11: # (at your option) any later version.
12: #
13: # LON-CAPA is distributed in the hope that it will be useful,
14: # but WITHOUT ANY WARRANTY; without even the implied warranty of
15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16: # GNU General Public License for more details.
17: #
18: # You should have received a copy of the GNU General Public License
19: # along with LON-CAPA; if not, write to the Free Software
20: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21: #
22: # /home/httpd/html/adm/gpl.txt
23: #
24: # http://www.lon-capa.org/
25: #
26: # (Navigate problems for statistical reports
1.28 matthew 27: #
28: #######################################################
29: #######################################################
30:
31: =pod
32:
33: =head1 NAME
34:
35: lonstudentassessment
36:
37: =head1 SYNOPSIS
38:
39: Presents assessment data about a student or a group of students.
40:
41: =head1 Subroutines
42:
43: =over 4
44:
45: =cut
46:
47: #######################################################
48: #######################################################
1.1 stredwic 49:
1.21 minaeibi 50: package Apache::lonstudentassessment;
1.1 stredwic 51:
52: use strict;
1.28 matthew 53: use Apache::lonstatistics;
1.1 stredwic 54: use Apache::lonhtmlcommon;
55: use Apache::loncoursedata;
1.28 matthew 56: use Apache::lonnet; # for logging porpoises
1.31 matthew 57: use Spreadsheet::WriteExcel;
58:
59: #######################################################
60: #######################################################
61: =pod
62:
63: =item Package Variables
64:
65: =over 4
66:
67: =item $Statistics Hash ref to store student data. Indexed by symb,
68: contains hashes with keys 'score' and 'max'.
69:
70: =cut
71:
72: #######################################################
73: #######################################################
1.1 stredwic 74:
1.30 matthew 75: my $Statistics;
76:
1.28 matthew 77: #######################################################
78: #######################################################
79:
80: =pod
81:
1.31 matthew 82: =item $show_links 'yes' or 'no' for linking to student performance data
83:
84: =item $output_mode 'html', 'excel', or 'csv' for output mode
85:
1.32 matthew 86: =item $show 'all', 'totals', or 'scores' determines how much data is output
1.31 matthew 87:
88: =cut
89:
90: #######################################################
91: #######################################################
92: my $show_links;
93: my $output_mode;
94: my $show;
1.28 matthew 95:
1.31 matthew 96: #######################################################
97: #######################################################
98: # End of package variable declarations
1.28 matthew 99:
1.31 matthew 100: =pod
1.28 matthew 101:
1.31 matthew 102: =back
1.28 matthew 103:
1.31 matthew 104: =cut
1.28 matthew 105:
1.31 matthew 106: #######################################################
107: #######################################################
1.28 matthew 108:
1.31 matthew 109: =pod
1.28 matthew 110:
1.31 matthew 111: =item &BuildStudentAssessmentPage()
1.28 matthew 112:
1.31 matthew 113: Inputs:
1.4 stredwic 114:
1.31 matthew 115: =over 4
1.28 matthew 116:
117: =item $r Apache Request
118:
119: =item $c Apache Connection
120:
121: =back
122:
123: =cut
124:
125: #######################################################
126: #######################################################
1.1 stredwic 127: sub BuildStudentAssessmentPage {
1.30 matthew 128: my ($r,$c)=@_;
129: undef($Statistics);
130: #
1.31 matthew 131: # Print out the HTML headers for the interface
132: # This also parses the output mode selector
133: # This step must always be done.
1.30 matthew 134: $r->print(&CreateInterface());
1.31 matthew 135: $r->print('<input type="hidden" name="notfirstrun" value="true" />');
1.7 stredwic 136: $r->rflush();
1.31 matthew 137: if (! exists($ENV{'form.notfirstrun'})) {
138: $r->print(<<ENDMSG);
139: <p>
140: <font size="+1">
141: Please make your selections in the boxes above and hit
142: the button marked "Update Display".
143: </font>
144: </p>
145: ENDMSG
1.33 matthew 146: # $r->print(&OutputDescriptions());
1.31 matthew 147: return;
148: }
149: #
150: #
151: my $initialize = \&html_initialize;
152: my $output_student = \&html_outputstudent;
153: my $finish = \&html_finish;
154: #
155: if ($output_mode eq 'excel') {
156: $initialize = \&excel_initialize;
157: $output_student = \&excel_outputstudent;
158: $finish = \&excel_finish;
1.35 matthew 159: } elsif ($output_mode eq 'multi-sheet excel') {
160: $initialize = \&multi_sheet_excel_initialize;
161: $output_student = \&multi_sheet_excel_outputstudent;
162: $finish = \&multi_sheet_excel_finish;
1.31 matthew 163: } elsif ($output_mode eq 'csv') {
164: $initialize = \&csv_initialize;
165: $output_student = \&csv_outputstudent;
166: $finish = \&csv_finish;
167: }
1.30 matthew 168: #
169: if($c->aborted()) { return ; }
1.31 matthew 170: #
171: # Call the initialize routine selected above
172: $initialize->($r);
1.30 matthew 173: foreach my $student (@Apache::lonstatistics::Students) {
1.31 matthew 174: if($c->aborted()) {
175: $finish->($r);
176: return ;
1.1 stredwic 177: }
1.31 matthew 178: # Call the output_student routine selected above
179: $output_student->($r,$student);
180: }
181: # Call the "finish" routine selected above
182: $finish->($r);
183: #
184: return;
185: }
186:
187: #######################################################
188: #######################################################
1.30 matthew 189:
1.31 matthew 190: sub get_student_fields_to_show {
191: my @to_show = @Apache::lonstatistics::SelectedStudentData;
192: foreach (@to_show) {
193: if ($_ eq 'all') {
194: @to_show = @Apache::lonstatistics::StudentDataOrder;
195: last;
196: }
197: }
198: return @to_show;
199: }
200:
201: sub get_sequences_to_show {
202: my @Sequences;
203: foreach my $map_symb (@Apache::lonstatistics::SelectedMaps) {
204: foreach my $sequence (@Apache::lonstatistics::Sequences) {
205: next if ($sequence->{'symb'} ne $map_symb && $map_symb ne 'all');
206: next if ($sequence->{'num_assess'} < 1);
207: push (@Sequences,$sequence);
208: }
1.1 stredwic 209: }
1.31 matthew 210: return @Sequences;
1.2 stredwic 211: }
1.30 matthew 212:
1.31 matthew 213:
1.28 matthew 214: #######################################################
215: #######################################################
216:
217: =pod
1.2 stredwic 218:
1.28 matthew 219: =item &CreateInterface()
1.21 minaeibi 220:
1.28 matthew 221: Called by &BuildStudentAssessmentPage to create the top part of the
222: page which displays the chart.
223:
1.30 matthew 224: Inputs: None
1.28 matthew 225:
226: Returns: A string containing the HTML for the headers and top table for
227: the chart page.
228:
229: =cut
230:
231: #######################################################
232: #######################################################
1.2 stredwic 233: sub CreateInterface {
1.4 stredwic 234: my $Str = '';
1.30 matthew 235: # $Str .= &CreateLegend();
236: $Str .= '<table cellspacing="5">'."\n";
237: $Str .= '<tr>';
238: $Str .= '<td align="center"><b>Sections</b></td>';
239: $Str .= '<td align="center"><b>Student Data</b></td>';
1.35 matthew 240: $Str .= '<td align="center"><b>Problem Sets</b></td>';
1.31 matthew 241: $Str .= '<td align="center"><b>Output Format</b></td>';
1.30 matthew 242: $Str .= '</tr>'."\n";
243: #
1.4 stredwic 244: $Str .= '<tr><td align="center">'."\n";
1.29 matthew 245: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.4 stredwic 246: $Str .= '</td><td align="center">';
1.30 matthew 247: my $only_seq_with_assessments = sub {
248: my $s=shift;
249: if ($s->{'num_assess'} < 1) {
250: return 0;
251: } else {
252: return 1;
253: }
254: };
255: $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
256: 5,undef);
1.4 stredwic 257: $Str .= '</td><td>'."\n";
1.30 matthew 258: $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
259: $only_seq_with_assessments);
1.31 matthew 260: $Str .= '</td><td>'."\n";
261: $Str .= &CreateAndParseOutputSelector();
1.30 matthew 262: $Str .= '</td></tr>'."\n";
263: $Str .= '</table>'."\n";
1.4 stredwic 264: return $Str;
1.1 stredwic 265: }
1.30 matthew 266:
267: #######################################################
268: #######################################################
269:
270: =pod
271:
1.31 matthew 272: =item &CreateAndParseOutputSelector()
1.30 matthew 273:
274: =cut
275:
276: #######################################################
277: #######################################################
1.32 matthew 278: my @OutputOptions =
279: ({ name => 'HTML, with links',
280: value => 'html, with links',
1.33 matthew 281: description => 'Output HTML with each symbol linked to the problem '.
1.35 matthew 282: 'which generated it.',
283: mode => 'html',
284: show => 'all',
285: show_links => 'yes',
286: },
1.32 matthew 287: { name => 'HTML, without links',
288: value => 'html, without links',
1.33 matthew 289: description => 'Output HTML. By not including links, the size of the'.
290: ' web page is greatly reduced. If your browser crashes on the '.
1.35 matthew 291: 'full display, try this.',
292: mode => 'html',
293: show => 'all',
294: show_links => 'no',
295: },
1.33 matthew 296: { name => 'HTML, scores only',
297: value => 'html, scores only',
298: description => 'Output HTML, only showing the total number of correct'.
299: ' problems (or problem parts) and not the maximum possible for '.
1.35 matthew 300: 'each student',
301: mode => 'html',
302: show => 'scores',
303: show_links => 'no',
304: },
1.32 matthew 305: { name => 'HTML, totals',
306: value => 'html, totals',
1.33 matthew 307: description => 'Output HTML, but only the summary statistics for each'.
1.39 ! matthew 308: ' sequence selected for each student.',
1.35 matthew 309: mode => 'html',
310: show => 'totals',
311: show_links => 'no',
312: },
1.39 ! matthew 313: { name => 'HTML, summary table only',
! 314: value => 'html summary table only',
! 315: description => 'Output HTML, but only the final summary table for '.
! 316: 'all students across all sequences.',
! 317: mode => 'html',
! 318: show => 'final table',
! 319: show_links => 'no',
! 320: },
1.33 matthew 321: { name => 'Excel, scores only',
322: value => 'excel, scores only',
323: description => 'Output an Excel file (compatable with Excel 95), '.
324: 'with a single column for each sequence showing the students '.
1.35 matthew 325: 'score.',
326: mode => 'excel',
327: show => 'scores',
328: show_links => 'no',
329: },
1.32 matthew 330: { name => 'Excel, totals',
331: value => 'excel, totals',
1.33 matthew 332: description => 'Output an Excel file (compatable with Excel 95), '.
333: 'with two columns for each sequence, the students score on the '.
1.35 matthew 334: 'sequence and the students maximum possible on the sequence',
335: mode => 'excel',
336: show => 'totals',
337: show_links => 'no',
338: },
339: { name => 'multi-sheet Excel',
340: value => 'multi-sheet excel',
341: description => 'Output an Excel file (compatable with Excel 95), '.
342: 'with a seperate worksheet for each sequence you have selected '.
343: 'the data for each problem part '.
344: '(number of tries, status, points awarded) will be listed.',
345: mode => 'multi-sheet excel',
346: show => 'totals',
347: show_links => 'no',
348: },
349: { name => 'multi-sheet Excel, by section',
350: value => 'multi-sheet excel, by section',
351: description => 'Output an Excel file (compatable with Excel 95), '.
352: 'with a seperate worksheet for each sequence you have selected '.
353: 'the data for each problem part '.
354: '(number of tries, status, points awarded) will be listed. '.
355: 'There will be one Excel workbook for each section selected.',
356: mode => 'multi-sheet excel',
357: show => 'by section',
358: show_links => 'no',
359: },
1.33 matthew 360: { name => 'CSV, everything',
361: value => 'csv, everything',
1.35 matthew 362: description => '',
363: mode => 'csv',
364: show => 'all',
365: show_links => 'no',
366: },
1.33 matthew 367: { name => 'CSV, scores only',
368: value => 'csv, scores only',
1.35 matthew 369: description => '',
370: mode => 'csv',
371: show => 'scores',
372: show_links => 'no',
373: },
1.32 matthew 374: { name => 'CSV, totals',
375: value => 'csv, totals',
1.35 matthew 376: description => '',
377: mode => 'csv',
378: show => 'totals',
379: show_links => 'no',
380: },
1.32 matthew 381: );
382:
1.33 matthew 383: sub OutputDescriptions {
384: my $Str = '';
385: $Str .= "<h2>Output Modes</h2>\n";
386: $Str .= "<dl>\n";
387: foreach my $outputmode (@OutputOptions) {
388: $Str .=" <dt>".$outputmode->{'name'}."</dt>\n";
389: $Str .=" <dd>".$outputmode->{'description'}."</dd>\n";
390: }
391: $Str .= "</dl>\n";
392: return $Str;
393: }
394:
1.31 matthew 395: sub CreateAndParseOutputSelector {
396: my $Str = '';
397: my $elementname = 'outputmode';
398: #
399: # Format for output options is 'mode, restrictions';
400: my $selected = 'html, with links';
401: if (exists($ENV{'form.'.$elementname})) {
402: if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
403: $selected = $ENV{'form.'.$elementname}->[0];
404: } else {
405: $selected = $ENV{'form.'.$elementname};
406: }
407: }
408: #
409: # Set package variables describing output mode
410: $show_links = 'no';
411: $output_mode = 'html';
412: $show = 'all';
1.35 matthew 413: foreach my $option (@OutputOptions) {
414: next if ($option->{'value'} ne $selected);
415: $output_mode = $option->{'mode'};
416: $show = $option->{'show'};
417: $show_links = $option->{'show_links'};
1.31 matthew 418: }
1.35 matthew 419:
1.31 matthew 420: #
421: # Build the form element
422: $Str = qq/<select size="5" name="$elementname">/;
1.32 matthew 423: foreach my $option (@OutputOptions) {
424: $Str .= "\n".' <option value="'.$option->{'value'}.'"';
425: $Str .= " selected " if ($option->{'value'} eq $selected);
426: $Str .= ">".$option->{'name'}."<\/option>";
1.31 matthew 427: }
428: $Str .= "\n</select>";
429: return $Str;
430: }
1.30 matthew 431:
1.28 matthew 432: #######################################################
433: #######################################################
1.1 stredwic 434:
1.28 matthew 435: =pod
436:
1.31 matthew 437: =head2 HTML output routines
1.28 matthew 438:
1.31 matthew 439: =item &html_initialize($r)
1.28 matthew 440:
1.31 matthew 441: Create labels for the columns of student data to show.
1.28 matthew 442:
1.31 matthew 443: =item &html_outputstudent($r,$student)
1.28 matthew 444:
1.31 matthew 445: Return a line of the chart for a student.
1.28 matthew 446:
1.31 matthew 447: =item &html_finish($r)
1.28 matthew 448:
449: =cut
450:
451: #######################################################
452: #######################################################
1.31 matthew 453: {
454: my $padding;
455: my $count;
456:
1.39 ! matthew 457: my $nodata_count; # The number of students for which there is no data
! 458: my %prog_state; # progress state used by loncommon PrgWin routines
! 459:
1.31 matthew 460: sub html_initialize {
461: my ($r) = @_;
1.30 matthew 462: #
463: $padding = ' 'x3;
1.35 matthew 464: $count = 0;
1.39 ! matthew 465: $nodata_count = 0;
1.30 matthew 466: #
1.38 matthew 467: $r->print("<h3>".$ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
468: " ".localtime(time)."</h3>");
1.39 ! matthew 469:
! 470: #
! 471: # Set up progress window for 'final table' display only
! 472: if ($show eq 'final table') {
! 473: my $studentcount = scalar(@Apache::lonstatistics::Students);
! 474: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
! 475: ($r,'Summary Table Status',
! 476: 'Summary Table Compilation Progress', $studentcount);
! 477: }
1.31 matthew 478: my $Str = "<pre>\n";
1.30 matthew 479: # First, the @StudentData fields need to be listed
1.31 matthew 480: my @to_show = &get_student_fields_to_show();
1.30 matthew 481: foreach my $field (@to_show) {
482: my $title=$Apache::lonstatistics::StudentData{$field}->{'title'};
483: my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'};
484: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
485: $Str .= $title.' 'x($width-$base).$padding;
486: }
487: # Now the selected sequences need to be listed
1.31 matthew 488: foreach my $sequence (&get_sequences_to_show) {
489: my $title = $sequence->{'title'};
490: my $base = $sequence->{'base_width'};
491: my $width = $sequence->{'width'};
492: $Str .= $title.' 'x($width-$base).$padding;
1.30 matthew 493: }
1.31 matthew 494: $Str .= "total (of shown problems)</pre>\n";
495: $Str .= "<pre>";
1.39 ! matthew 496: #
! 497: # Check for suppression of output
! 498: if ($show eq 'final table') {
! 499: $Str = '';
! 500: }
1.31 matthew 501: $r->print($Str);
502: $r->rflush();
503: return;
1.30 matthew 504: }
505:
1.31 matthew 506: sub html_outputstudent {
507: my ($r,$student) = @_;
1.2 stredwic 508: my $Str = '';
1.35 matthew 509: #
510: if($count++ % 5 == 0 && $count > 0) {
511: $r->print("</pre><pre>");
512: }
1.30 matthew 513: # First, the @StudentData fields need to be listed
1.31 matthew 514: my @to_show = &get_student_fields_to_show();
1.30 matthew 515: foreach my $field (@to_show) {
516: my $title=$student->{$field};
1.31 matthew 517: my $base = length($title);
1.30 matthew 518: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
519: $Str .= $title.' 'x($width-$base).$padding;
520: }
521: # Get ALL the students data
522: my %StudentsData;
523: my @tmp = &Apache::loncoursedata::get_current_state
524: ($student->{'username'},$student->{'domain'},undef,
525: $ENV{'request.course.id'});
526: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
527: %StudentsData = @tmp;
528: }
529: if (scalar(@tmp) < 1) {
1.39 ! matthew 530: $nodata_count++;
! 531: return if ($show eq 'final table');
1.30 matthew 532: $Str .= '<font color="blue">No Course Data</font>'."\n";
1.31 matthew 533: $r->print($Str);
534: $r->rflush();
535: return;
1.30 matthew 536: }
537: #
538: # By sequence build up the data
539: my $studentstats;
1.31 matthew 540: my $PerformanceStr = '';
541: foreach my $seq (&get_sequences_to_show) {
542: my ($performance,$score,$seq_max) =
543: &StudentPerformanceOnSequence($student,\%StudentsData,
544: $seq,$show_links);
545: my $ratio = $score.'/'.$seq_max;
546: #
547: if ($show eq 'totals') {
548: $performance = ' 'x(length($seq_max)-length($score)).$ratio;
549: $performance .= ' 'x($seq->{'width'}-length($performance));
1.32 matthew 550: } elsif ($show eq 'scores') {
551: $performance = $score;
552: $performance .= ' 'x($seq->{'width'}-length($performance));
1.31 matthew 553: } else {
554: # Pad with extra spaces
555: $performance .= ' 'x($seq->{'width'}-$seq_max-
556: length($ratio)
557: ).$ratio;
1.30 matthew 558: }
1.31 matthew 559: #
560: $Str .= $performance.$padding;
561: #
562: $studentstats->{$seq->{'symb'}}->{'score'}= $score;
563: $studentstats->{$seq->{'symb'}}->{'max'} = $seq_max;
1.30 matthew 564: }
565: #
566: # Total it up and store the statistics info.
567: my ($score,$max) = (0,0);
568: while (my ($symb,$seq_stats) = each (%{$studentstats})) {
569: $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
570: $Statistics->{$symb}->{'max'} += $seq_stats->{'max'};
571: $score += $seq_stats->{'score'};
572: $max += $seq_stats->{'max'};
573: }
1.31 matthew 574: $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
1.30 matthew 575: $Str .= " \n";
1.39 ! matthew 576: #
! 577: # Check for suppressed output and update the progress window if so...
! 578: if ($show eq 'final table') {
! 579: $Str = '';
! 580: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
! 581: 'last student');
! 582: }
! 583: #
1.31 matthew 584: $r->print($Str);
585: #
586: $r->rflush();
587: return;
1.30 matthew 588: }
1.2 stredwic 589:
1.31 matthew 590: sub html_finish {
591: my ($r) = @_;
1.39 ! matthew 592: #
! 593: # Check for suppressed output and close the progress window if so
! 594: if ($show eq 'final table') {
! 595: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
! 596: } else {
! 597: $r->print("</pre>\n");
! 598: }
! 599: $r->print(&StudentAverageTotal());
1.31 matthew 600: $r->rflush();
601: return;
602: }
603:
1.39 ! matthew 604: sub StudentAverageTotal {
! 605: my $Str = "<h3>Summary Tables</h3>\n";
! 606: my $num_students = scalar(@Apache::lonstatistics::Students);
! 607: my $total_ave = 0;
! 608: my $total_max = 0;
! 609: $Str .= '<table border=2 cellspacing="1">'."\n";
! 610: $Str .= "<tr><th>Title</th><th>Average</th><th>Maximum</th></tr>\n";
! 611: foreach my $seq (&get_sequences_to_show) {
! 612: my $ave = int(100*($Statistics->{$seq->{'symb'}}->{'score'}/
! 613: ($num_students-$nodata_count)))/100;
! 614: $total_ave += $ave;
! 615: my $max = $seq->{'num_assess_parts'};
! 616: $total_max += $max;
! 617: if ($ave == 0) {
! 618: $ave = "0.00";
! 619: }
! 620: $ave .= ' ';
! 621: $max .= ' ';
! 622: $Str .= '<tr><td>'.$seq->{'title'}.'</td>'.
! 623: '<td align="right">'.$ave.'</td>'.
! 624: '<td align="right">'.$max.'</td></tr>'."\n";
! 625: }
! 626: $total_ave = int(100*$total_ave)/100; # only two digit
! 627: $Str .= "</table>\n";
! 628: $Str .= '<table border=2 cellspacing="1">'."\n";
! 629: $Str .= '<tr><th>Number of Students</th><th>Average</th>'.
! 630: "<th>Maximum</th></tr>\n";
! 631: $Str .= '<tr><td>'.($num_students-$nodata_count).'</td>'.
! 632: '<td>'.$total_ave.'</td><td>'.$total_max.'</td>';
! 633: $Str .= "</table>\n";
! 634: return $Str;
! 635: }
! 636:
1.31 matthew 637: }
638:
1.35 matthew 639: #######################################################
640: #######################################################
641:
642: =pod
643:
644: =head2 Multi-Sheet EXCEL subroutines
645:
646: =item &multi_sheet_excel_initialize($r)
647:
648: =item &multi_sheet_excel_outputstudent($r,$student)
649:
650: =item &multi_sheet_excel_finish($r)
651:
652: =cut
653:
654: #######################################################
655: #######################################################
656: {
657:
658: sub multi_sheet_excel_initialize {
659: my ($r)=@_;
660: $r->print("<h1>Not yet implemented</h1>");
661: #
662: # Estimate the size of the file. We would like to have < 5 megs of data.
663: my $max_size = 5000000;
664: my $num_students = scalar(@Apache::lonstatistics::Students);
665: my $num_sequences = 0;
666: my $num_data_per_part = 2; # 'status' and 'numtries'
667: my $fields_per_student = scalar(&get_student_fields_to_show());
668: my $bytes_per_field = 20; # Back of the envelope calculation
669: foreach my $seq (&get_sequences_to_show) {
670: $num_sequences++ if ($seq->{'num_assess'} > 0);
671: $fields_per_student += $num_data_per_part * $seq->{'num_assess_parts'};
672: }
673: my $size_estimate = $fields_per_student*$num_students*$bytes_per_field;
674: #
675: # Compute number of workbooks
676: my $num_workbooks = 1;
677: if ($size_estimate > $max_size) { # try to stay under 5 megs
678: $num_workbooks += int($size_estimate / $max_size);
679: }
680: if ($show eq 'by section') {
681: if (@Apache::lonstatistics::SelectedSections > 1 &&
682: $Apache::lonstatistics::SelectedSections[0] ne 'all') {
683: $num_workbooks = scalar(@Apache::lonstatistics::SelectedSections);
684: } else {
685: # @Apache::lonstatistics::Sections contains 'all' as well.
686: $num_workbooks = scalar(@Apache::lonstatistics::Sections) - 1;
687: }
688: }
689:
690: $r->print("Maximum allowed size: ".$max_size." bytes<br />");
691: $r->print("Number of students: ".$num_students."<br />");
692: $r->print("Number of fields per student: ".$fields_per_student."<br />");
693: $r->print("Total number of fields: ".($fields_per_student*$num_students).
694: "<br />");
695: $r->print("Bytes per field: ".$bytes_per_field." (estimated)"."<br />");
696: $r->print("Estimated size: ".$size_estimate." bytes<br />");
697: $r->print("Number of workbooks: ".$num_workbooks."<br />");
698: $r->rflush();
699: return;
700: }
701:
702: sub multi_sheet_excel_outputstudent {
703: my ($r,$student) = @_;
704: }
705:
706: sub multi_sheet_excel_finish {
707: my ($r) = @_;
708: }
709:
710: }
1.31 matthew 711: #######################################################
712: #######################################################
713:
714: =pod
715:
716: =head2 EXCEL subroutines
717:
718: =item &excel_initialize($r)
719:
720: =item &excel_outputstudent($r,$student)
721:
722: =item &excel_finish($r)
723:
724: =cut
725:
726: #######################################################
727: #######################################################
728: {
729:
730: my $excel_sheet;
1.32 matthew 731: my $excel_workbook;
732:
733: my $filename;
734: my $rows_output;
735: my $cols_output;
736:
1.36 matthew 737: my %prog_state; # progress window state
1.31 matthew 738:
739: sub excel_initialize {
740: my ($r) = @_;
741: #
1.32 matthew 742: $filename = '/prtspool/'.
1.31 matthew 743: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
744: time.'_'.rand(1000000000).'.xls';
1.32 matthew 745: #
746: $excel_workbook = undef;
747: $excel_sheet = undef;
748: #
749: $rows_output = 0;
750: $cols_output = 0;
751: #
752: # Create sheet
753: $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
754: #
755: # Check for errors
756: if (! defined($excel_workbook)) {
1.31 matthew 757: $r->log_error("Error creating excel spreadsheet $filename: $!");
758: $r->print("Problems creating new Excel file. ".
759: "This error has been logged. ".
760: "Please alert your LON-CAPA administrator");
1.32 matthew 761: return ;
1.31 matthew 762: }
763: #
764: # The excel spreadsheet stores temporary data in files, then put them
765: # together. If needed we should be able to disable this (memory only).
766: # The temporary directory must be specified before calling 'addworksheet'.
767: # File::Temp is used to determine the temporary directory.
1.32 matthew 768: $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
769: #
770: # Add a worksheet
1.33 matthew 771: my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
772: if (length($sheetname) > 31) {
773: $sheetname = substr($sheetname,0,31);
774: }
775: $excel_sheet = $excel_workbook->addworksheet($sheetname);
1.32 matthew 776: #
1.34 matthew 777: # Put the course description in the header
778: $excel_sheet->write($rows_output,$cols_output++,
779: $ENV{'course.'.$ENV{'request.course.id'}.'.description'});
780: $cols_output += 3;
781: #
782: # Put a description of the sections listed
783: my $sectionstring = '';
784: my @Sections = @Apache::lonstatistics::SelectedSections;
785: if (scalar(@Sections) > 1) {
786: if (scalar(@Sections) > 2) {
787: my $last = pop(@Sections);
788: $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;
789: } else {
790: $sectionstring = "Sections ".join(' and ',@Sections);
791: }
792: } else {
793: if ($Sections[0] eq 'all') {
794: $sectionstring = "All sections";
795: } else {
796: $sectionstring = "Section ".$Sections[0];
797: }
798: }
799: $excel_sheet->write($rows_output,$cols_output++,$sectionstring);
800: $cols_output += scalar(@Sections);
801: #
802: # Put the date in there too
803: $excel_sheet->write($rows_output,$cols_output++,
804: 'Compiled on '.localtime(time));
805: #
806: $rows_output++;
807: #
1.32 matthew 808: # Add the student headers
1.34 matthew 809: $cols_output = 0;
1.32 matthew 810: foreach my $field (&get_student_fields_to_show()) {
1.34 matthew 811: $excel_sheet->write($rows_output,$cols_output++,$field);
1.32 matthew 812: }
813: #
814: # Add the Sequence Headers
815: foreach my $seq (&get_sequences_to_show) {
1.34 matthew 816: $excel_sheet->write($rows_output,$cols_output,$seq->{'title'});
1.32 matthew 817: if ($show eq 'totals') {
1.34 matthew 818: $excel_sheet->write($rows_output+1,$cols_output,'score');
819: $excel_sheet->write($rows_output+1,$cols_output+1,'maximum');
1.32 matthew 820: $cols_output += 2;
821: } else {
822: $cols_output++;
823: }
824: }
825: #
826: # Bookkeeping
827: if ($show eq 'totals') {
1.34 matthew 828: $rows_output += 2;
1.32 matthew 829: } else {
1.34 matthew 830: $rows_output += 1;
1.32 matthew 831: }
832: #
833: # Let the user know what we are doing
834: my $studentcount = scalar(@Apache::lonstatistics::Students);
835: $r->print("<h1>Compiling Excel spreadsheet for ".
836: $studentcount.' student');
837: $r->print('s') if ($studentcount > 1);
838: $r->print("</h1>\n");
839: $r->rflush();
1.31 matthew 840: #
1.36 matthew 841: # Initialize progress window
842: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
843: ($r,'Excel File Compilation Status',
844: 'Excel File Compilation Progress', $studentcount);
845: #
1.31 matthew 846: return;
847: }
848:
849: sub excel_outputstudent {
850: my ($r,$student) = @_;
1.32 matthew 851: return if (! defined($excel_sheet));
852: $cols_output=0;
853: #
854: # Write out student data
855: my @to_show = &get_student_fields_to_show();
856: foreach my $field (@to_show) {
857: $excel_sheet->write($rows_output,$cols_output++,$student->{$field});
858: }
859: #
860: # Get student assessment data
861: my %StudentsData;
862: my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
863: $student->{'domain'},
864: undef,
865: $ENV{'request.course.id'});
866: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
867: %StudentsData = @tmp;
868: }
869: #
870: # Write out sequence scores and totals data
871: foreach my $seq (&get_sequences_to_show) {
872: my ($performance,$score,$seq_max) =
873: &StudentPerformanceOnSequence($student,\%StudentsData,
874: $seq,'no');
875: if ($show eq 'totals' || $show eq 'scores') {
876: $excel_sheet->write($rows_output,$cols_output++,$score);
877: }
878: if ($show eq 'totals') {
879: $excel_sheet->write($rows_output,$cols_output++,$seq_max);
880: }
881: }
882: #
883: # Bookkeeping
884: $rows_output++;
885: $cols_output=0;
886: #
1.36 matthew 887: # Update the progress window
888: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
1.32 matthew 889: return;
1.31 matthew 890: }
891:
892: sub excel_finish {
893: my ($r) = @_;
1.32 matthew 894: return if (! defined($excel_sheet));
895: #
896: # Write the excel file
897: $excel_workbook->close();
898: my $c = $r->connection();
899: #
900: return if($c->aborted());
901: #
1.36 matthew 902: # Close the progress window
903: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
904: #
1.32 matthew 905: # Tell the user where to get their excel file
1.36 matthew 906: $r->print('<br />'.
1.32 matthew 907: '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
908: $r->rflush();
909: return;
1.31 matthew 910: }
911:
912: }
1.30 matthew 913: #######################################################
914: #######################################################
915:
916: =pod
917:
1.31 matthew 918: =head2 CSV output routines
919:
920: =item &csv_initialize($r)
921:
922: =item &csv_outputstudent($r,$student)
923:
924: =item &csv_finish($r)
1.30 matthew 925:
926: =cut
927:
928: #######################################################
929: #######################################################
1.31 matthew 930: {
931:
1.37 matthew 932: my $outputfile;
933: my $filename;
934:
935: my %prog_state; # progress window state
936:
1.31 matthew 937: sub csv_initialize{
938: my ($r) = @_;
1.37 matthew 939: #
940: # Clean up
941: $filename = undef;
942: $outputfile = undef;
943: undef(%prog_state);
944: #
945: # Open a file
946: $filename = '/prtspool/'.
947: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
948: time.'_'.rand(1000000000).'.csv';
949: unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) {
950: $r->log_error("Couldn't open $filename for output $!");
951: $r->print("Problems occured in writing the csv file. ".
952: "This error has been logged. ".
953: "Please alert your LON-CAPA administrator.");
954: $outputfile = undef;
955: }
1.38 matthew 956: #
957: # Datestamp
958: my $description = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
959: print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'.
960: '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'.
961: "\n";
962:
1.37 matthew 963: #
964: # Print out the headings
965: my $Str = '';
966: my $Str2 = undef;
967: foreach my $field (&get_student_fields_to_show()) {
968: if ($show eq 'scores') {
969: $Str .= '"'.&Apache::loncommon::csv_translate($field).'",';
970: } elsif ($show eq 'totals') {
971: $Str .= '"",'; # first row empty on the student fields
972: $Str2 .= '"'.&Apache::loncommon::csv_translate($field).'",';
973: } elsif ($show eq 'all') {
974: $Str .= '"'.&Apache::loncommon::csv_translate($field).'",';
975: }
976: }
977: foreach my $seq (&get_sequences_to_show) {
978: if ($show eq 'scores') {
979: $Str .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).
980: '",';
981: } elsif ($show eq 'totals') {
982: $Str .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).
983: '","",';
984: $Str2 .= '"score","total possible",';
985: } elsif ($show eq 'all') {
986: $Str .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).
987: '",';
988: $Str .= '"",'x($seq->{'num_assess_parts'}-1);
989: $Str .= '"score","total possible",';
990: }
991: }
992: chop($Str);
993: $Str .= "\n";
994: print $outputfile $Str;
995: if (defined($Str2)) {
996: chop($Str2);
997: $Str2 .= "\n";
998: print $outputfile $Str2;
999: }
1000: #
1001: # Initialize progress window
1002: my $studentcount = scalar(@Apache::lonstatistics::Students);
1003: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
1004: ($r,'CSV File Compilation Status',
1005: 'CSV File Compilation Progress', $studentcount);
1.31 matthew 1006: return;
1007: }
1008:
1009: sub csv_outputstudent {
1010: my ($r,$student) = @_;
1.37 matthew 1011: return if (! defined($outputfile));
1012: my $Str = '';
1013: #
1014: # Output student fields
1015: my @to_show = &get_student_fields_to_show();
1016: foreach my $field (@to_show) {
1017: $Str .= '"'.&Apache::loncommon::csv_translate($student->{$field}).'",';
1018: }
1019: #
1020: # Get student assessment data
1021: my %StudentsData;
1022: my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
1023: $student->{'domain'},
1024: undef,
1025: $ENV{'request.course.id'});
1026: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
1027: %StudentsData = @tmp;
1028: }
1029: #
1030: # Output performance data
1031: foreach my $seq (&get_sequences_to_show) {
1032: my ($performance,$score,$seq_max) =
1033: &StudentPerformanceOnSequence($student,\%StudentsData,
1034: $seq,'no');
1035: if ($show eq 'scores') {
1036: $Str .= '"'.$score.'",';
1037: } elsif ($show eq 'totals') {
1038: $Str .= '"'.$score.'","'.$seq_max.'",';
1039: } elsif ($show eq 'all') {
1040: $Str .= '"'.join('","',(split(//,$performance),$score,$seq_max)).
1041: '",';
1042: }
1043: }
1044: chop($Str);
1045: $Str .= "\n";
1046: print $outputfile $Str;
1047: #
1048: # Update the progress window
1049: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
1050: return;
1.31 matthew 1051: }
1052:
1053: sub csv_finish {
1054: my ($r) = @_;
1.37 matthew 1055: return if (! defined($outputfile));
1056: close($outputfile);
1057: #
1058: my $c = $r->connection();
1059: return if ($c->aborted());
1060: #
1061: # Close the progress window
1062: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
1063: #
1064: # Tell the user where to get their csv file
1065: $r->print('<br />'.
1066: '<a href="'.$filename.'">Your csv file.</a>'."\n");
1067: $r->rflush();
1068: return;
1069:
1.31 matthew 1070: }
1.2 stredwic 1071:
1072: }
1073:
1.28 matthew 1074: #######################################################
1075: #######################################################
1076:
1.2 stredwic 1077: =pod
1078:
1.30 matthew 1079: =item &StudentPerformanceOnSequence()
1.2 stredwic 1080:
1.30 matthew 1081: Inputs:
1.2 stredwic 1082:
1083: =over 4
1084:
1.30 matthew 1085: =item $student
1.28 matthew 1086:
1.30 matthew 1087: =item $studentdata Hash ref to all student data
1.2 stredwic 1088:
1.30 matthew 1089: =item $seq Hash ref, the sequence we are working on
1.2 stredwic 1090:
1.30 matthew 1091: =item $links if defined we will output links to each resource.
1.2 stredwic 1092:
1.28 matthew 1093: =back
1.2 stredwic 1094:
1095: =cut
1.1 stredwic 1096:
1.28 matthew 1097: #######################################################
1098: #######################################################
1.30 matthew 1099: sub StudentPerformanceOnSequence {
1.32 matthew 1100: my ($student,$studentdata,$seq,$links) = @_;
1.31 matthew 1101: $links = 'no' if (! defined($links));
1.1 stredwic 1102: my $Str = '';
1.30 matthew 1103: my ($sum,$max) = (0,0);
1104: foreach my $resource (@{$seq->{'contents'}}) {
1105: next if ($resource->{'type'} ne 'assessment');
1106: my $resource_data = $studentdata->{$resource->{'symb'}};
1107: my $value = '';
1108: foreach my $partnum (@{$resource->{'parts'}}) {
1109: $max++;
1110: my $symbol = ' '; # default to space
1111: #
1112: if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
1113: my $status = $resource_data->{'resource.'.$partnum.'.solved'};
1114: if ($status eq 'correct_by_override') {
1115: $symbol = '+';
1116: $sum++;
1117: } elsif ($status eq 'incorrect_by_override') {
1118: $symbol = '-';
1119: } elsif ($status eq 'ungraded_attempted') {
1120: $symbol = '#';
1121: } elsif ($status eq 'incorrect_attempted') {
1122: $symbol = '.';
1123: } elsif ($status eq 'excused') {
1124: $symbol = 'x';
1125: $max--;
1126: } elsif ($status eq 'correct_by_student' &&
1127: exists($resource_data->{'resource.'.$partnum.'.tries'})){
1128: my $num = $resource_data->{'resource.'.$partnum.'.tries'};
1129: if ($num > 9) {
1130: $symbol = '*';
1131: } elsif ($num > 0) {
1132: $symbol = $num;
1133: } else {
1134: $symbol = ' ';
1135: }
1136: $sum++;
1137: } else {
1138: $symbol = ' ';
1.2 stredwic 1139: }
1.30 matthew 1140: } else {
1141: # Unsolved. Did they try?
1142: if (exists($resource_data->{'resource.'.$partnum.'.tries'})){
1143: $symbol = '.';
1144: } else {
1145: $symbol = ' ';
1.18 matthew 1146: }
1.2 stredwic 1147: }
1.30 matthew 1148: #
1.31 matthew 1149: if ($links eq 'yes' && $symbol ne ' ') {
1.30 matthew 1150: $symbol = '<a href="/adm/grades'.
1151: '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
1152: '&student='.$student->{'username'}.
1153: '&domain='.$student->{'domain'}.
1154: '&command=submission">'.$symbol.'</a>';
1155: }
1156: $value .= $symbol;
1.2 stredwic 1157: }
1.30 matthew 1158: $Str .= $value;
1.17 minaeibi 1159: }
1.30 matthew 1160: return ($Str,$sum,$max);
1.1 stredwic 1161: }
1.23 minaeibi 1162:
1.28 matthew 1163: #######################################################
1164: #######################################################
1.23 minaeibi 1165:
1.2 stredwic 1166: =pod
1167:
1168: =item &CreateLegend()
1169:
1170: This function returns a formatted string containing the legend for the
1171: chart. The legend describes the symbols used to represent grades for
1172: problems.
1173:
1174: =cut
1175:
1.28 matthew 1176: #######################################################
1177: #######################################################
1.2 stredwic 1178: sub CreateLegend {
1179: my $Str = "<p><pre>".
1.13 minaeibi 1180: " 1 correct by student in 1 try\n".
1181: " 7 correct by student in 7 tries\n".
1.12 minaeibi 1182: " * correct by student in more than 9 tries\n".
1.20 minaeibi 1183: " + correct by hand grading or override\n".
1.12 minaeibi 1184: " - incorrect by override\n".
1185: " . incorrect attempted\n".
1186: " # ungraded attempted\n".
1.13 minaeibi 1187: " not attempted (blank field)\n".
1.12 minaeibi 1188: " x excused".
1.17 minaeibi 1189: "</pre><p>";
1.2 stredwic 1190: return $Str;
1191: }
1192:
1.28 matthew 1193: #######################################################
1194: #######################################################
1195:
1.30 matthew 1196: =pod
1.2 stredwic 1197:
1198: =back
1199:
1200: =cut
1201:
1.28 matthew 1202: #######################################################
1203: #######################################################
1.2 stredwic 1204:
1.28 matthew 1205: 1;
1.2 stredwic 1206:
1.1 stredwic 1207: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>