1: # The LearningOnline Network with CAPA
2: #
3: # $Id: lonstudentassessment.pm,v 1.36 2003/03/06 21:12:54 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: 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: }
209: }
210: return @Sequences;
211: }
212:
213:
214: #######################################################
215: #######################################################
216:
217: =pod
218:
219: =item &CreateInterface()
220:
221: Called by &BuildStudentAssessmentPage to create the top part of the
222: page which displays the chart.
223:
224: Inputs: None
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: #######################################################
233: sub CreateInterface {
234: my $Str = '';
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>';
240: $Str .= '<td align="center"><b>Problem Sets</b></td>';
241: $Str .= '<td align="center"><b>Output Format</b></td>';
242: $Str .= '</tr>'."\n";
243: #
244: $Str .= '<tr><td align="center">'."\n";
245: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
246: $Str .= '</td><td align="center">';
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);
257: $Str .= '</td><td>'."\n";
258: $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
259: $only_seq_with_assessments);
260: $Str .= '</td><td>'."\n";
261: $Str .= &CreateAndParseOutputSelector();
262: $Str .= '</td></tr>'."\n";
263: $Str .= '</table>'."\n";
264: return $Str;
265: }
266:
267: #######################################################
268: #######################################################
269:
270: =pod
271:
272: =item &CreateAndParseOutputSelector()
273:
274: =cut
275:
276: #######################################################
277: #######################################################
278: my @OutputOptions =
279: ({ name => 'HTML, with links',
280: value => 'html, with links',
281: description => 'Output HTML with each symbol linked to the problem '.
282: 'which generated it.',
283: mode => 'html',
284: show => 'all',
285: show_links => 'yes',
286: },
287: { name => 'HTML, without links',
288: value => 'html, without links',
289: description => 'Output HTML. By not including links, the size of the'.
290: ' web page is greatly reduced. If your browser crashes on the '.
291: 'full display, try this.',
292: mode => 'html',
293: show => 'all',
294: show_links => 'no',
295: },
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 '.
300: 'each student',
301: mode => 'html',
302: show => 'scores',
303: show_links => 'no',
304: },
305: { name => 'HTML, totals',
306: value => 'html, totals',
307: description => 'Output HTML, but only the summary statistics for each'.
308: ' sequence selected.',
309: mode => 'html',
310: show => 'totals',
311: show_links => 'no',
312: },
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 '.
317: 'score.',
318: mode => 'excel',
319: show => 'scores',
320: show_links => 'no',
321: },
322: { name => 'Excel, totals',
323: value => 'excel, totals',
324: description => 'Output an Excel file (compatable with Excel 95), '.
325: 'with two columns for each sequence, the students score on the '.
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: },
352: { name => 'CSV, everything',
353: value => 'csv, everything',
354: description => '',
355: mode => 'csv',
356: show => 'all',
357: show_links => 'no',
358: },
359: { name => 'CSV, scores only',
360: value => 'csv, scores only',
361: description => '',
362: mode => 'csv',
363: show => 'scores',
364: show_links => 'no',
365: },
366: { name => 'CSV, totals',
367: value => 'csv, totals',
368: description => '',
369: mode => 'csv',
370: show => 'totals',
371: show_links => 'no',
372: },
373: );
374:
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:
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';
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'};
410: }
411:
412: #
413: # Build the form element
414: $Str = qq/<select size="5" name="$elementname">/;
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>";
419: }
420: $Str .= "\n</select>";
421: return $Str;
422: }
423:
424: #######################################################
425: #######################################################
426:
427: =pod
428:
429: =head2 HTML output routines
430:
431: =item &html_initialize($r)
432:
433: Create labels for the columns of student data to show.
434:
435: =item &html_outputstudent($r,$student)
436:
437: Return a line of the chart for a student.
438:
439: =item &html_finish($r)
440:
441: =cut
442:
443: #######################################################
444: #######################################################
445: {
446: my $padding;
447: my $count;
448:
449: sub html_initialize {
450: my ($r) = @_;
451: #
452: $padding = ' 'x3;
453: $count = 0;
454: #
455: my $Str = "<pre>\n";
456: # First, the @StudentData fields need to be listed
457: my @to_show = &get_student_fields_to_show();
458: foreach my $field (@to_show) {
459: my $title=$Apache::lonstatistics::StudentData{$field}->{'title'};
460: my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'};
461: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
462: $Str .= $title.' 'x($width-$base).$padding;
463: }
464: # Now the selected sequences need to be listed
465: foreach my $sequence (&get_sequences_to_show) {
466: my $title = $sequence->{'title'};
467: my $base = $sequence->{'base_width'};
468: my $width = $sequence->{'width'};
469: $Str .= $title.' 'x($width-$base).$padding;
470: }
471: $Str .= "total (of shown problems)</pre>\n";
472: $Str .= "<pre>";
473: $r->print($Str);
474: $r->rflush();
475: return;
476: }
477:
478: sub html_outputstudent {
479: my ($r,$student) = @_;
480: my $Str = '';
481: #
482: if($count++ % 5 == 0 && $count > 0) {
483: $r->print("</pre><pre>");
484: }
485: # First, the @StudentData fields need to be listed
486: my @to_show = &get_student_fields_to_show();
487: foreach my $field (@to_show) {
488: my $title=$student->{$field};
489: my $base = length($title);
490: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
491: $Str .= $title.' 'x($width-$base).$padding;
492: }
493: # Get ALL the students data
494: my %StudentsData;
495: my @tmp = &Apache::loncoursedata::get_current_state
496: ($student->{'username'},$student->{'domain'},undef,
497: $ENV{'request.course.id'});
498: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
499: %StudentsData = @tmp;
500: }
501: if (scalar(@tmp) < 1) {
502: $Str .= '<font color="blue">No Course Data</font>'."\n";
503: $r->print($Str);
504: $r->rflush();
505: return;
506: }
507: #
508: # By sequence build up the data
509: my $studentstats;
510: my $PerformanceStr = '';
511: foreach my $seq (&get_sequences_to_show) {
512: my ($performance,$score,$seq_max) =
513: &StudentPerformanceOnSequence($student,\%StudentsData,
514: $seq,$show_links);
515: my $ratio = $score.'/'.$seq_max;
516: #
517: if ($show eq 'totals') {
518: $performance = ' 'x(length($seq_max)-length($score)).$ratio;
519: $performance .= ' 'x($seq->{'width'}-length($performance));
520: } elsif ($show eq 'scores') {
521: $performance = $score;
522: $performance .= ' 'x($seq->{'width'}-length($performance));
523: } else {
524: # Pad with extra spaces
525: $performance .= ' 'x($seq->{'width'}-$seq_max-
526: length($ratio)
527: ).$ratio;
528: }
529: #
530: $Str .= $performance.$padding;
531: #
532: $studentstats->{$seq->{'symb'}}->{'score'}= $score;
533: $studentstats->{$seq->{'symb'}}->{'max'} = $seq_max;
534: }
535: #
536: # Total it up and store the statistics info.
537: my ($score,$max) = (0,0);
538: while (my ($symb,$seq_stats) = each (%{$studentstats})) {
539: $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
540: $Statistics->{$symb}->{'max'} += $seq_stats->{'max'};
541: $score += $seq_stats->{'score'};
542: $max += $seq_stats->{'max'};
543: }
544: $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
545: $Str .= " \n";
546: $r->print($Str);
547: #
548: $r->rflush();
549: return;
550: }
551:
552: sub html_finish {
553: my ($r) = @_;
554: $r->print("</pre>\n");
555: $r->rflush();
556: return;
557: }
558:
559: }
560:
561: #######################################################
562: #######################################################
563:
564: =pod
565:
566: =head2 Multi-Sheet EXCEL subroutines
567:
568: =item &multi_sheet_excel_initialize($r)
569:
570: =item &multi_sheet_excel_outputstudent($r,$student)
571:
572: =item &multi_sheet_excel_finish($r)
573:
574: =cut
575:
576: #######################################################
577: #######################################################
578: {
579:
580: sub multi_sheet_excel_initialize {
581: my ($r)=@_;
582: $r->print("<h1>Not yet implemented</h1>");
583: #
584: # Estimate the size of the file. We would like to have < 5 megs of data.
585: my $max_size = 5000000;
586: my $num_students = scalar(@Apache::lonstatistics::Students);
587: my $num_sequences = 0;
588: my $num_data_per_part = 2; # 'status' and 'numtries'
589: my $fields_per_student = scalar(&get_student_fields_to_show());
590: my $bytes_per_field = 20; # Back of the envelope calculation
591: foreach my $seq (&get_sequences_to_show) {
592: $num_sequences++ if ($seq->{'num_assess'} > 0);
593: $fields_per_student += $num_data_per_part * $seq->{'num_assess_parts'};
594: }
595: my $size_estimate = $fields_per_student*$num_students*$bytes_per_field;
596: #
597: # Compute number of workbooks
598: my $num_workbooks = 1;
599: if ($size_estimate > $max_size) { # try to stay under 5 megs
600: $num_workbooks += int($size_estimate / $max_size);
601: }
602: if ($show eq 'by section') {
603: if (@Apache::lonstatistics::SelectedSections > 1 &&
604: $Apache::lonstatistics::SelectedSections[0] ne 'all') {
605: $num_workbooks = scalar(@Apache::lonstatistics::SelectedSections);
606: } else {
607: # @Apache::lonstatistics::Sections contains 'all' as well.
608: $num_workbooks = scalar(@Apache::lonstatistics::Sections) - 1;
609: }
610: }
611:
612: $r->print("Maximum allowed size: ".$max_size." bytes<br />");
613: $r->print("Number of students: ".$num_students."<br />");
614: $r->print("Number of fields per student: ".$fields_per_student."<br />");
615: $r->print("Total number of fields: ".($fields_per_student*$num_students).
616: "<br />");
617: $r->print("Bytes per field: ".$bytes_per_field." (estimated)"."<br />");
618: $r->print("Estimated size: ".$size_estimate." bytes<br />");
619: $r->print("Number of workbooks: ".$num_workbooks."<br />");
620: $r->rflush();
621: return;
622: }
623:
624: sub multi_sheet_excel_outputstudent {
625: my ($r,$student) = @_;
626: }
627:
628: sub multi_sheet_excel_finish {
629: my ($r) = @_;
630: }
631:
632: }
633: #######################################################
634: #######################################################
635:
636: =pod
637:
638: =head2 EXCEL subroutines
639:
640: =item &excel_initialize($r)
641:
642: =item &excel_outputstudent($r,$student)
643:
644: =item &excel_finish($r)
645:
646: =cut
647:
648: #######################################################
649: #######################################################
650: {
651:
652: my $excel_sheet;
653: my $excel_workbook;
654:
655: my $filename;
656: my $rows_output;
657: my $cols_output;
658:
659: my %prog_state; # progress window state
660:
661: sub excel_initialize {
662: my ($r) = @_;
663: #
664: $filename = '/prtspool/'.
665: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
666: time.'_'.rand(1000000000).'.xls';
667: #
668: $excel_workbook = undef;
669: $excel_sheet = undef;
670: #
671: $rows_output = 0;
672: $cols_output = 0;
673: #
674: # Create sheet
675: $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
676: #
677: # Check for errors
678: if (! defined($excel_workbook)) {
679: $r->log_error("Error creating excel spreadsheet $filename: $!");
680: $r->print("Problems creating new Excel file. ".
681: "This error has been logged. ".
682: "Please alert your LON-CAPA administrator");
683: return ;
684: }
685: #
686: # The excel spreadsheet stores temporary data in files, then put them
687: # together. If needed we should be able to disable this (memory only).
688: # The temporary directory must be specified before calling 'addworksheet'.
689: # File::Temp is used to determine the temporary directory.
690: $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
691: #
692: # Add a worksheet
693: my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
694: if (length($sheetname) > 31) {
695: $sheetname = substr($sheetname,0,31);
696: }
697: $excel_sheet = $excel_workbook->addworksheet($sheetname);
698: #
699: # Put the course description in the header
700: $excel_sheet->write($rows_output,$cols_output++,
701: $ENV{'course.'.$ENV{'request.course.id'}.'.description'});
702: $cols_output += 3;
703: #
704: # Put a description of the sections listed
705: my $sectionstring = '';
706: my @Sections = @Apache::lonstatistics::SelectedSections;
707: if (scalar(@Sections) > 1) {
708: if (scalar(@Sections) > 2) {
709: my $last = pop(@Sections);
710: $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;
711: } else {
712: $sectionstring = "Sections ".join(' and ',@Sections);
713: }
714: } else {
715: if ($Sections[0] eq 'all') {
716: $sectionstring = "All sections";
717: } else {
718: $sectionstring = "Section ".$Sections[0];
719: }
720: }
721: $excel_sheet->write($rows_output,$cols_output++,$sectionstring);
722: $cols_output += scalar(@Sections);
723: #
724: # Put the date in there too
725: $excel_sheet->write($rows_output,$cols_output++,
726: 'Compiled on '.localtime(time));
727: #
728: $rows_output++;
729: #
730: # Add the student headers
731: $cols_output = 0;
732: foreach my $field (&get_student_fields_to_show()) {
733: $excel_sheet->write($rows_output,$cols_output++,$field);
734: }
735: #
736: # Add the Sequence Headers
737: foreach my $seq (&get_sequences_to_show) {
738: $excel_sheet->write($rows_output,$cols_output,$seq->{'title'});
739: if ($show eq 'totals') {
740: $excel_sheet->write($rows_output+1,$cols_output,'score');
741: $excel_sheet->write($rows_output+1,$cols_output+1,'maximum');
742: $cols_output += 2;
743: } else {
744: $cols_output++;
745: }
746: }
747: #
748: # Bookkeeping
749: if ($show eq 'totals') {
750: $rows_output += 2;
751: } else {
752: $rows_output += 1;
753: }
754: #
755: # Let the user know what we are doing
756: my $studentcount = scalar(@Apache::lonstatistics::Students);
757: $r->print("<h1>Compiling Excel spreadsheet for ".
758: $studentcount.' student');
759: $r->print('s') if ($studentcount > 1);
760: $r->print("</h1>\n");
761: $r->rflush();
762: #
763: # Initialize progress window
764: %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
765: ($r,'Excel File Compilation Status',
766: 'Excel File Compilation Progress', $studentcount);
767: #
768: return;
769: }
770:
771: sub excel_outputstudent {
772: my ($r,$student) = @_;
773: return if (! defined($excel_sheet));
774: $cols_output=0;
775: #
776: # Write out student data
777: my @to_show = &get_student_fields_to_show();
778: foreach my $field (@to_show) {
779: $excel_sheet->write($rows_output,$cols_output++,$student->{$field});
780: }
781: #
782: # Get student assessment data
783: my %StudentsData;
784: my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
785: $student->{'domain'},
786: undef,
787: $ENV{'request.course.id'});
788: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
789: %StudentsData = @tmp;
790: }
791: #
792: # Write out sequence scores and totals data
793: foreach my $seq (&get_sequences_to_show) {
794: my ($performance,$score,$seq_max) =
795: &StudentPerformanceOnSequence($student,\%StudentsData,
796: $seq,'no');
797: if ($show eq 'totals' || $show eq 'scores') {
798: $excel_sheet->write($rows_output,$cols_output++,$score);
799: }
800: if ($show eq 'totals') {
801: $excel_sheet->write($rows_output,$cols_output++,$seq_max);
802: }
803: }
804: #
805: # Bookkeeping
806: $rows_output++;
807: $cols_output=0;
808: #
809: # Update the progress window
810: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
811: return;
812: }
813:
814: sub excel_finish {
815: my ($r) = @_;
816: return if (! defined($excel_sheet));
817: #
818: # Write the excel file
819: $excel_workbook->close();
820: my $c = $r->connection();
821: #
822: return if($c->aborted());
823: #
824: # Close the progress window
825: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
826: #
827: # Tell the user where to get their excel file
828: $r->print('<br />'.
829: '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
830: $r->rflush();
831: return;
832: }
833:
834: }
835: #######################################################
836: #######################################################
837:
838: =pod
839:
840: =head2 CSV output routines
841:
842: =item &csv_initialize($r)
843:
844: =item &csv_outputstudent($r,$student)
845:
846: =item &csv_finish($r)
847:
848: =cut
849:
850: #######################################################
851: #######################################################
852: {
853:
854: sub csv_initialize{
855: my ($r) = @_;
856: $r->print("<h1>Not implemented yet</h1>");
857: return;
858: }
859:
860: sub csv_outputstudent {
861: my ($r,$student) = @_;
862: }
863:
864: sub csv_finish {
865: my ($r) = @_;
866: }
867:
868: }
869:
870: #######################################################
871: #######################################################
872:
873: =pod
874:
875: =item &StudentPerformanceOnSequence()
876:
877: Inputs:
878:
879: =over 4
880:
881: =item $student
882:
883: =item $studentdata Hash ref to all student data
884:
885: =item $seq Hash ref, the sequence we are working on
886:
887: =item $links if defined we will output links to each resource.
888:
889: =back
890:
891: =cut
892:
893: #######################################################
894: #######################################################
895: sub StudentPerformanceOnSequence {
896: my ($student,$studentdata,$seq,$links) = @_;
897: $links = 'no' if (! defined($links));
898: my $Str = '';
899: my ($sum,$max) = (0,0);
900: foreach my $resource (@{$seq->{'contents'}}) {
901: next if ($resource->{'type'} ne 'assessment');
902: my $resource_data = $studentdata->{$resource->{'symb'}};
903: my $value = '';
904: foreach my $partnum (@{$resource->{'parts'}}) {
905: $max++;
906: my $symbol = ' '; # default to space
907: #
908: if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
909: my $status = $resource_data->{'resource.'.$partnum.'.solved'};
910: if ($status eq 'correct_by_override') {
911: $symbol = '+';
912: $sum++;
913: } elsif ($status eq 'incorrect_by_override') {
914: $symbol = '-';
915: } elsif ($status eq 'ungraded_attempted') {
916: $symbol = '#';
917: } elsif ($status eq 'incorrect_attempted') {
918: $symbol = '.';
919: } elsif ($status eq 'excused') {
920: $symbol = 'x';
921: $max--;
922: } elsif ($status eq 'correct_by_student' &&
923: exists($resource_data->{'resource.'.$partnum.'.tries'})){
924: my $num = $resource_data->{'resource.'.$partnum.'.tries'};
925: if ($num > 9) {
926: $symbol = '*';
927: } elsif ($num > 0) {
928: $symbol = $num;
929: } else {
930: $symbol = ' ';
931: }
932: $sum++;
933: } else {
934: $symbol = ' ';
935: }
936: } else {
937: # Unsolved. Did they try?
938: if (exists($resource_data->{'resource.'.$partnum.'.tries'})){
939: $symbol = '.';
940: } else {
941: $symbol = ' ';
942: }
943: }
944: #
945: if ($links eq 'yes' && $symbol ne ' ') {
946: $symbol = '<a href="/adm/grades'.
947: '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
948: '&student='.$student->{'username'}.
949: '&domain='.$student->{'domain'}.
950: '&command=submission">'.$symbol.'</a>';
951: }
952: $value .= $symbol;
953: }
954: $Str .= $value;
955: }
956: return ($Str,$sum,$max);
957: }
958:
959: #######################################################
960: #######################################################
961: sub StudentAverageTotal {
962: my ($cache, $students, $sequenceKeys)=@_;
963: my $Str = "\n<b>Summary Tables:</b>\n";
964: my %Correct = ();
965: my $ProblemsSolved = 0;
966: my $TotalProblems = 0;
967: my $StudentCount = 0;
968:
969: foreach my $name (@$students) {
970: $StudentCount++;
971: foreach my $sequence (@$sequenceKeys) {
972: $Correct{$sequence} +=
973: $cache->{$name.':'.$sequence.':problemsCorrect'};
974: }
975: $ProblemsSolved += $cache->{$name.':problemsSolved'};
976: $TotalProblems += $cache->{$name.':totalProblems'};
977: }
978: if ($StudentCount) {
979: $ProblemsSolved = sprintf( "%.2f",
980: $ProblemsSolved/$StudentCount);
981: $TotalProblems /= $StudentCount;
982: } else {
983: $ProblemsSolved = 0;
984: $TotalProblems = 0;
985: }
986:
987: $Str .= '<table border=2 cellspacing="1">'."\n";
988: $Str .= '<tr><td><b>Students Count</b></td><td><b>'.
989: $StudentCount.'</b></td></tr>'."\n";
990: $Str .= '<tr><td><b>Total Problems</b></td><td><b>'.
991: $TotalProblems.'</b></td></tr>'."\n";
992: $Str .= '<tr><td><b>Average Correct</b></td><td><b>'.
993: $ProblemsSolved.'</b></td></tr>'."\n";
994: $Str .= '</table>'."\n";
995:
996: $Str .= '<table border=2 cellspacing="1">'."\n";
997: $Str .= '<tr><th>Title</th><th>Total Problems</th>'.
998: '<th>Average Correct</th></tr>'."\n";
999: foreach my $S(@$sequenceKeys) {
1000: my $title=$cache->{$S.':title'};
1001: #$Str .= $cache->{$S.':problems'};
1002: #my @problems=split(':', $cache->{$S.':problems'});
1003: #my $pCount=scalar @problems;
1004: my $pCount=MaxSeqPr($cache,@$students[0],$S);
1005: my $crr;
1006: if ($StudentCount) {
1007: $crr=sprintf( "%.2f", $Correct{$S}/$StudentCount );
1008: } else {
1009: $crr="0.00";
1010: }
1011: $Str .= '<tr><td>'.$title.
1012: '</td><td align=center>'.$pCount.
1013: '</td><td align=center>'.$crr.
1014: '</td></tr>'."\n";
1015: }
1016:
1017: $Str .= '</table>'."\n";
1018:
1019: return $Str;
1020: }
1021:
1022: #######################################################
1023: #######################################################
1024:
1025: =pod
1026:
1027: =item &CreateLegend()
1028:
1029: This function returns a formatted string containing the legend for the
1030: chart. The legend describes the symbols used to represent grades for
1031: problems.
1032:
1033: =cut
1034:
1035: #######################################################
1036: #######################################################
1037: sub CreateLegend {
1038: my $Str = "<p><pre>".
1039: " 1 correct by student in 1 try\n".
1040: " 7 correct by student in 7 tries\n".
1041: " * correct by student in more than 9 tries\n".
1042: " + correct by hand grading or override\n".
1043: " - incorrect by override\n".
1044: " . incorrect attempted\n".
1045: " # ungraded attempted\n".
1046: " not attempted (blank field)\n".
1047: " x excused".
1048: "</pre><p>";
1049: return $Str;
1050: }
1051:
1052: #######################################################
1053: #######################################################
1054:
1055: =pod
1056:
1057: =back
1058:
1059: =cut
1060:
1061: #######################################################
1062: #######################################################
1063:
1064: 1;
1065:
1066: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>