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