File:
[LON-CAPA] /
loncom /
interface /
statistics /
lonstudentassessment.pm
Revision
1.31:
download - view:
text,
annotated -
select for diffs
Mon Mar 3 19:28:29 2003 UTC (21 years, 4 months ago) by
matthew
Branches:
MAIN
CVS tags:
HEAD
Big Changes:
Started adding different output options.
No longer begins computing before the user has made selections.
The code is being simplified to some degree and obfuscated in other ways.
Each output type has three subroutines associated with it: initialize,
outputstudent, and finish. Function pointers are used (defaulting the output
mode to 'html'). Currently excel and csv output both result in
"Not Implemented".
Renamed &CreateTableHeadings to &html_initialize, &ChartOutputStudent to
&html_outputstudent, and added &html_finish. Each of these routines was
changed to make it responsible for *all* html output of the chart, other than
the selection form.
Still remaining to be filled in are the routines for csv and excel output.
1: # The LearningOnline Network with CAPA
2: #
3: # $Id: lonstudentassessment.pm,v 1.31 2003/03/03 19:28:29 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' or 'totals' 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: return;
147: }
148: #
149: #
150: my $initialize = \&html_initialize;
151: my $output_student = \&html_outputstudent;
152: my $finish = \&html_finish;
153: #
154: if ($output_mode eq 'excel') {
155: $initialize = \&excel_initialize;
156: $output_student = \&excel_outputstudent;
157: $finish = \&excel_finish;
158: } elsif ($output_mode eq 'csv') {
159: $initialize = \&csv_initialize;
160: $output_student = \&csv_outputstudent;
161: $finish = \&csv_finish;
162: }
163: #
164: if($c->aborted()) { return ; }
165: #
166: # Call the initialize routine selected above
167: $initialize->($r);
168: foreach my $student (@Apache::lonstatistics::Students) {
169: if($c->aborted()) {
170: $finish->($r);
171: return ;
172: }
173: # Call the output_student routine selected above
174: $output_student->($r,$student);
175: }
176: # Call the "finish" routine selected above
177: $finish->($r);
178: #
179: return;
180: }
181:
182: #######################################################
183: #######################################################
184:
185: sub get_student_fields_to_show {
186: my @to_show = @Apache::lonstatistics::SelectedStudentData;
187: foreach (@to_show) {
188: if ($_ eq 'all') {
189: @to_show = @Apache::lonstatistics::StudentDataOrder;
190: last;
191: }
192: }
193: return @to_show;
194: }
195:
196: sub get_sequences_to_show {
197: my @Sequences;
198: foreach my $map_symb (@Apache::lonstatistics::SelectedMaps) {
199: foreach my $sequence (@Apache::lonstatistics::Sequences) {
200: next if ($sequence->{'symb'} ne $map_symb && $map_symb ne 'all');
201: next if ($sequence->{'num_assess'} < 1);
202: push (@Sequences,$sequence);
203: }
204: }
205: return @Sequences;
206: }
207:
208:
209: #######################################################
210: #######################################################
211:
212: =pod
213:
214: =item &CreateInterface()
215:
216: Called by &BuildStudentAssessmentPage to create the top part of the
217: page which displays the chart.
218:
219: Inputs: None
220:
221: Returns: A string containing the HTML for the headers and top table for
222: the chart page.
223:
224: =cut
225:
226: #######################################################
227: #######################################################
228: sub CreateInterface {
229: my $Str = '';
230: # $Str .= &CreateLegend();
231: $Str .= '<table cellspacing="5">'."\n";
232: $Str .= '<tr>';
233: $Str .= '<td align="center"><b>Sections</b></td>';
234: $Str .= '<td align="center"><b>Student Data</b></td>';
235: $Str .= '<td align="center"><b>Sequences and Folders</b></td>';
236: $Str .= '<td align="center"><b>Output Format</b></td>';
237: $Str .= '</tr>'."\n";
238: #
239: $Str .= '<tr><td align="center">'."\n";
240: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
241: $Str .= '</td><td align="center">';
242: my $only_seq_with_assessments = sub {
243: my $s=shift;
244: if ($s->{'num_assess'} < 1) {
245: return 0;
246: } else {
247: return 1;
248: }
249: };
250: $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
251: 5,undef);
252: $Str .= '</td><td>'."\n";
253: $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
254: $only_seq_with_assessments);
255: $Str .= '</td><td>'."\n";
256: $Str .= &CreateAndParseOutputSelector();
257: $Str .= '</td></tr>'."\n";
258: $Str .= '</table>'."\n";
259: return $Str;
260: }
261:
262: #######################################################
263: #######################################################
264:
265: =pod
266:
267: =item &CreateAndParseOutputSelector()
268:
269: =cut
270:
271: #######################################################
272: #######################################################
273: sub CreateAndParseOutputSelector {
274: my $Str = '';
275: my $elementname = 'outputmode';
276: #
277: # Format for output options is 'mode, restrictions';
278: my @Options = ('html, with links','html, without links',
279: 'html, totals only','excel, totals only',
280: 'csv, totals only','csv, everything');
281: my $selected = 'html, with links';
282: if (exists($ENV{'form.'.$elementname})) {
283: if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
284: $selected = $ENV{'form.'.$elementname}->[0];
285: } else {
286: $selected = $ENV{'form.'.$elementname};
287: }
288: }
289: #
290: # Set package variables describing output mode
291: $show_links = 'no';
292: $output_mode = 'html';
293: $show = 'all';
294: my ($mode,$restriction) = split(',',$selected);
295: $restriction =~ s/^\s*//;
296: if ($mode =~ /^(html|excel|csv)$/) {
297: $output_mode = $mode;
298: } else {
299: $output_mode = 'html';
300: }
301: if ($restriction eq 'with links') {
302: $show_links = 'yes';
303: } else {
304: $show_links = 'no';
305: }
306: if ($restriction eq 'totals only') {
307: $show = 'totals';
308: } else {
309: $show = 'everything';
310: }
311: #
312: # Build the form element
313: $Str = qq/<select size="5" name="$elementname">/;
314: foreach my $option (@Options) {
315: $Str .= qq/\n <option value="$option"/;
316: $Str .= " selected " if ($option eq $selected);
317: $Str .= ">$option<\/option>";
318: }
319: $Str .= "\n</select>";
320: return $Str;
321: }
322:
323: #######################################################
324: #######################################################
325:
326: =pod
327:
328: =head2 HTML output routines
329:
330: =item &html_initialize($r)
331:
332: Create labels for the columns of student data to show.
333:
334: =item &html_outputstudent($r,$student)
335:
336: Return a line of the chart for a student.
337:
338: =item &html_finish($r)
339:
340: =cut
341:
342: #######################################################
343: #######################################################
344: {
345: my $padding;
346: my $count;
347:
348: sub html_initialize {
349: my ($r) = @_;
350: #
351: $padding = ' 'x3;
352: $count = 1;
353: #
354: my $Str = "<pre>\n";
355: # First, the @StudentData fields need to be listed
356: my @to_show = &get_student_fields_to_show();
357: foreach my $field (@to_show) {
358: my $title=$Apache::lonstatistics::StudentData{$field}->{'title'};
359: my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'};
360: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
361: $Str .= $title.' 'x($width-$base).$padding;
362: }
363: # Now the selected sequences need to be listed
364: foreach my $sequence (&get_sequences_to_show) {
365: my $title = $sequence->{'title'};
366: my $base = $sequence->{'base_width'};
367: my $width = $sequence->{'width'};
368: $Str .= $title.' 'x($width-$base).$padding;
369: }
370: $Str .= "total (of shown problems)</pre>\n";
371: $Str .= "<pre>";
372: $r->print($Str);
373: $r->rflush();
374: return;
375: }
376:
377: sub html_outputstudent {
378: my ($r,$student) = @_;
379: my $Str = '';
380: # First, the @StudentData fields need to be listed
381: my @to_show = &get_student_fields_to_show();
382: foreach my $field (@to_show) {
383: my $title=$student->{$field};
384: my $base = length($title);
385: my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
386: $Str .= $title.' 'x($width-$base).$padding;
387: }
388: # Get ALL the students data
389: my %StudentsData;
390: my @tmp = &Apache::loncoursedata::get_current_state
391: ($student->{'username'},$student->{'domain'},undef,
392: $ENV{'request.course.id'});
393: if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
394: %StudentsData = @tmp;
395: }
396: if (scalar(@tmp) < 1) {
397: $Str .= '<font color="blue">No Course Data</font>'."\n";
398: $r->print($Str);
399: $r->rflush();
400: return;
401: }
402: #
403: # By sequence build up the data
404: my $studentstats;
405: my $PerformanceStr = '';
406: foreach my $seq (&get_sequences_to_show) {
407: my ($performance,$score,$seq_max) =
408: &StudentPerformanceOnSequence($student,\%StudentsData,
409: $seq,$show_links);
410: my $ratio = $score.'/'.$seq_max;
411: #
412: if ($show eq 'totals') {
413: $performance = ' 'x(length($seq_max)-length($score)).$ratio;
414: $performance .= ' 'x($seq->{'width'}-length($performance));
415: } else {
416: # Pad with extra spaces
417: $performance .= ' 'x($seq->{'width'}-$seq_max-
418: length($ratio)
419: ).$ratio;
420: }
421: #
422: $Str .= $performance.$padding;
423: #
424: $studentstats->{$seq->{'symb'}}->{'score'}= $score;
425: $studentstats->{$seq->{'symb'}}->{'max'} = $seq_max;
426: }
427: #
428: # Total it up and store the statistics info.
429: my ($score,$max) = (0,0);
430: while (my ($symb,$seq_stats) = each (%{$studentstats})) {
431: $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
432: $Statistics->{$symb}->{'max'} += $seq_stats->{'max'};
433: $score += $seq_stats->{'score'};
434: $max += $seq_stats->{'max'};
435: }
436: $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
437: $Str .= " \n";
438: $r->print($Str);
439: #
440: $count++;
441: if($count % 5 == 0) {
442: $r->print("</pre><pre>");
443: }
444: #
445: $r->rflush();
446: return;
447: }
448:
449: sub html_finish {
450: my ($r) = @_;
451: $r->print("</pre>\n");
452: $r->rflush();
453: return;
454: }
455:
456: }
457:
458: #######################################################
459: #######################################################
460:
461: =pod
462:
463: =head2 EXCEL subroutines
464:
465: =item &excel_initialize($r)
466:
467: =item &excel_outputstudent($r,$student)
468:
469: =item &excel_finish($r)
470:
471: =cut
472:
473: #######################################################
474: #######################################################
475: {
476:
477: my $excel_sheet;
478:
479: sub excel_initialize {
480: my ($r) = @_;
481: #
482: $r->print("<h1>Not implemented yet</h1>");
483: return;
484: my $filename = '/prtspool/'.
485: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
486: time.'_'.rand(1000000000).'.xls';
487: $excel_sheet = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
488: if (! defined($excel_sheet)) {
489: $r->log_error("Error creating excel spreadsheet $filename: $!");
490: $r->print("Problems creating new Excel file. ".
491: "This error has been logged. ".
492: "Please alert your LON-CAPA administrator");
493: return 0;
494: }
495: #
496: # The excel spreadsheet stores temporary data in files, then put them
497: # together. If needed we should be able to disable this (memory only).
498: # The temporary directory must be specified before calling 'addworksheet'.
499: # File::Temp is used to determine the temporary directory.
500: $excel_sheet->set_tempdir($Apache::lonnet::tmpdir);
501: #
502: # Determine the name to give the worksheet
503: # $excel_sheet->addworksheet();
504:
505: return;
506: }
507:
508: sub excel_outputstudent {
509: my ($r,$student) = @_;
510: }
511:
512: sub excel_finish {
513: my ($r) = @_;
514: }
515:
516: }
517: #######################################################
518: #######################################################
519:
520: =pod
521:
522: =head2 CSV output routines
523:
524: =item &csv_initialize($r)
525:
526: =item &csv_outputstudent($r,$student)
527:
528: =item &csv_finish($r)
529:
530: =cut
531:
532: #######################################################
533: #######################################################
534: {
535:
536: sub csv_initialize{
537: my ($r) = @_;
538: $r->print("<h1>Not implemented yet</h1>");
539: return;
540: }
541:
542: sub csv_outputstudent {
543: my ($r,$student) = @_;
544: }
545:
546: sub csv_finish {
547: my ($r) = @_;
548: }
549:
550: }
551:
552: #######################################################
553: #######################################################
554:
555: =pod
556:
557: =item &StudentPerformanceOnSequence()
558:
559: Inputs:
560:
561: =over 4
562:
563: =item $student
564:
565: =item $studentdata Hash ref to all student data
566:
567: =item $seq Hash ref, the sequence we are working on
568:
569: =item $links if defined we will output links to each resource.
570:
571: =back
572:
573: =cut
574:
575: #######################################################
576: #######################################################
577: sub StudentPerformanceOnSequence {
578: my ($student,$studentdata,$seq,$links,$totalonly) = @_;
579: $totalonly = 0 if (! defined($totalonly));
580: $links = 'no' if (! defined($links));
581: my $Str = '';
582: my ($sum,$max) = (0,0);
583: foreach my $resource (@{$seq->{'contents'}}) {
584: next if ($resource->{'type'} ne 'assessment');
585: my $resource_data = $studentdata->{$resource->{'symb'}};
586: my $value = '';
587: foreach my $partnum (@{$resource->{'parts'}}) {
588: $max++;
589: my $symbol = ' '; # default to space
590: #
591: if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
592: my $status = $resource_data->{'resource.'.$partnum.'.solved'};
593: if ($status eq 'correct_by_override') {
594: $symbol = '+';
595: $sum++;
596: } elsif ($status eq 'incorrect_by_override') {
597: $symbol = '-';
598: } elsif ($status eq 'ungraded_attempted') {
599: $symbol = '#';
600: } elsif ($status eq 'incorrect_attempted') {
601: $symbol = '.';
602: } elsif ($status eq 'excused') {
603: $symbol = 'x';
604: $max--;
605: } elsif ($status eq 'correct_by_student' &&
606: exists($resource_data->{'resource.'.$partnum.'.tries'})){
607: my $num = $resource_data->{'resource.'.$partnum.'.tries'};
608: if ($num > 9) {
609: $symbol = '*';
610: } elsif ($num > 0) {
611: $symbol = $num;
612: } else {
613: $symbol = ' ';
614: }
615: $sum++;
616: } else {
617: $symbol = ' ';
618: }
619: } else {
620: # Unsolved. Did they try?
621: if (exists($resource_data->{'resource.'.$partnum.'.tries'})){
622: $symbol = '.';
623: } else {
624: $symbol = ' ';
625: }
626: }
627: #
628: if ($links eq 'yes' && $symbol ne ' ') {
629: $symbol = '<a href="/adm/grades'.
630: '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
631: '&student='.$student->{'username'}.
632: '&domain='.$student->{'domain'}.
633: '&command=submission">'.$symbol.'</a>';
634: }
635: $value .= $symbol;
636: }
637: $Str .= $value;
638: }
639: return ($Str,$sum,$max);
640: }
641:
642: #######################################################
643: #######################################################
644: sub StudentAverageTotal {
645: my ($cache, $students, $sequenceKeys)=@_;
646: my $Str = "\n<b>Summary Tables:</b>\n";
647: my %Correct = ();
648: my $ProblemsSolved = 0;
649: my $TotalProblems = 0;
650: my $StudentCount = 0;
651:
652: foreach my $name (@$students) {
653: $StudentCount++;
654: foreach my $sequence (@$sequenceKeys) {
655: $Correct{$sequence} +=
656: $cache->{$name.':'.$sequence.':problemsCorrect'};
657: }
658: $ProblemsSolved += $cache->{$name.':problemsSolved'};
659: $TotalProblems += $cache->{$name.':totalProblems'};
660: }
661: if ($StudentCount) {
662: $ProblemsSolved = sprintf( "%.2f",
663: $ProblemsSolved/$StudentCount);
664: $TotalProblems /= $StudentCount;
665: } else {
666: $ProblemsSolved = 0;
667: $TotalProblems = 0;
668: }
669:
670: $Str .= '<table border=2 cellspacing="1">'."\n";
671: $Str .= '<tr><td><b>Students Count</b></td><td><b>'.
672: $StudentCount.'</b></td></tr>'."\n";
673: $Str .= '<tr><td><b>Total Problems</b></td><td><b>'.
674: $TotalProblems.'</b></td></tr>'."\n";
675: $Str .= '<tr><td><b>Average Correct</b></td><td><b>'.
676: $ProblemsSolved.'</b></td></tr>'."\n";
677: $Str .= '</table>'."\n";
678:
679: $Str .= '<table border=2 cellspacing="1">'."\n";
680: $Str .= '<tr><th>Title</th><th>Total Problems</th>'.
681: '<th>Average Correct</th></tr>'."\n";
682: foreach my $S(@$sequenceKeys) {
683: my $title=$cache->{$S.':title'};
684: #$Str .= $cache->{$S.':problems'};
685: #my @problems=split(':', $cache->{$S.':problems'});
686: #my $pCount=scalar @problems;
687: my $pCount=MaxSeqPr($cache,@$students[0],$S);
688: my $crr;
689: if ($StudentCount) {
690: $crr=sprintf( "%.2f", $Correct{$S}/$StudentCount );
691: } else {
692: $crr="0.00";
693: }
694: $Str .= '<tr><td>'.$title.
695: '</td><td align=center>'.$pCount.
696: '</td><td align=center>'.$crr.
697: '</td></tr>'."\n";
698: }
699:
700: $Str .= '</table>'."\n";
701:
702: return $Str;
703: }
704:
705: #######################################################
706: #######################################################
707:
708: =pod
709:
710: =item &CreateLegend()
711:
712: This function returns a formatted string containing the legend for the
713: chart. The legend describes the symbols used to represent grades for
714: problems.
715:
716: =cut
717:
718: #######################################################
719: #######################################################
720: sub CreateLegend {
721: my $Str = "<p><pre>".
722: " 1 correct by student in 1 try\n".
723: " 7 correct by student in 7 tries\n".
724: " * correct by student in more than 9 tries\n".
725: " + correct by hand grading or override\n".
726: " - incorrect by override\n".
727: " . incorrect attempted\n".
728: " # ungraded attempted\n".
729: " not attempted (blank field)\n".
730: " x excused".
731: "</pre><p>";
732: return $Str;
733: }
734:
735: #######################################################
736: #######################################################
737:
738: =pod
739:
740: =back
741:
742: =cut
743:
744: #######################################################
745: #######################################################
746:
747: 1;
748:
749: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>