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