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