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