1: # The LearningOnline Network with CAPA
2: # (Publication Handler
3: #
4: # $Id: lonstudentassessment.pm,v 1.2 2002/07/25 21:23:51 stredwic Exp $
5: #
6: # Copyright Michigan State University Board of Trustees
7: #
8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
9: #
10: # LON-CAPA is free software; you can redistribute it and/or modify
11: # it under the terms of the GNU General Public License as published by
12: # the Free Software Foundation; either version 2 of the License, or
13: # (at your option) any later version.
14: #
15: # LON-CAPA is distributed in the hope that it will be useful,
16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: # GNU General Public License for more details.
19: #
20: # You should have received a copy of the GNU General Public License
21: # along with LON-CAPA; if not, write to the Free Software
22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23: #
24: # /home/httpd/html/adm/gpl.txt
25: #
26: # http://www.lon-capa.org/
27: #
28: # (Navigate problems for statistical reports
29: # YEAR=2001
30: # 5/5,7/9,7/25/1,8/11,9/13,9/26,10/5,10/9,10/22,10/26 Behrouz Minaei
31: # 11/1,11/4,11/16,12/14,12/16,12/18,12/20,12/31 Behrouz Minaei
32: # YEAR=2002
33: # 1/22,2/1,2/6,2/25,3/2,3/6,3/17,3/21,3/22,3/26,4/7,5/6 Behrouz Minaei
34: # 5/12,5/14,5/15,5/19,5/26,7/16 Behrouz Minaei
35: #
36: ###
37:
38: package Apache::lonstudentassessment;
39:
40: use strict;
41: use Apache::lonhtmlcommon;
42: use Apache::loncoursedata;
43: use GDBM_File;
44:
45: sub BuildStudentAssessmentPage {
46: my ($cacheDB,$students,$courseID,$formName,$headings,$spacing,
47: $studentInformation,$r,$c)=@_;
48:
49: my %cache;
50: unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER,0640)) {
51: $r->print('<html><body>Unable to tie database.</body></html>');
52: return;
53: }
54: my $selectedName = &FindSelectedStudent(\%cache,
55: $cache{'StudentAssessmentStudent'},
56: $students);
57: $r->print(&CreateInterface(\%cache, $selectedName, $students, $formName));
58:
59: my $Ptr = '';
60: if($selectedName eq 'No Student Selected') {
61: $Ptr .= '<h3><font color=blue>WARNING: ';
62: $Ptr .= 'Please select a student</font></h3>';
63: $r->print($Ptr);
64: return;
65: }
66:
67: my ($infoHeadings, $infoKeys, $sequenceHeadings, $sequenceKeys) =
68: &ShouldShowColumns(\%cache, $headings, $studentInformation);
69:
70: $r->print(&CreateTableHeadings(\%cache, $spacing, $infoKeys, $infoHeadings,
71: $sequenceKeys, $sequenceHeadings));
72: untie(%cache);
73:
74: my $selected=0;
75: $r->print('<pre>'."\n");
76: foreach (@$students) {
77: next if ($_ ne $selectedName &&
78: $selectedName ne 'All Students');
79: $selected = 1;
80: my $courseData;
81: if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER,0640)) {
82: if($cache{$_.':lastDownloadTime'} eq 'Not downloaded') {
83: untie(%cache);
84: $courseData =
85: &Apache::loncoursedata::DownloadCourseInformation($_,
86: $courseID);
87: if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT,0640)) {
88: &Apache::loncoursedata::ProcessStudentData(\%cache,
89: $courseData, $_);
90: untie(%cache);
91: } else {
92: last if($c->aborted());
93: next;
94: }
95: } else {
96: untie(%cache);
97: }
98: } else {
99: last if($c->aborted());
100: next;
101: }
102:
103: last if ($c->aborted());
104:
105: if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER,0640)) {
106: my $displayString = 'DISPLAYDATA'.$spacing;
107: $r->print(&Apache::lonhtmlcommon::FormatStudentInformation(
108: \%cache, $_,
109: $infoKeys,
110: $displayString,
111: 'preformatted'));
112: $r->print(&StudentReport(\%cache, $_, $spacing, $sequenceKeys));
113: $r->print("\n");
114: untie(%cache);
115: }
116: }
117: $r->print('</pre>'."\n");
118: if($selected == 0) {
119: $Ptr .= '<h3><font color=blue>WARNING: ';
120: $Ptr .= 'Please select a student</font></h3>';
121: $r->print($Ptr);
122: }
123:
124: return;
125: }
126:
127: #---- Student Assessment Web Page --------------------------------------------
128:
129: sub CreateInterface {
130: my($cache,$selectedName,$students,$formName)=@_;
131: my $Ptr = '';
132: $Ptr .= &CreateLegend();
133: $Ptr .= '<table><tr><td>'."\n";
134: $Ptr .= '<input type="submit" name="PreviousStudent" ';
135: $Ptr .= 'value="Previous Student" />'."\n";
136: $Ptr .= '   '."\n";
137: $Ptr .= &Apache::lonhtmlcommon::StudentOptions($cache, $students,
138: $selectedName,
139: 'StudentAssessment',
140: $formName);
141: $Ptr .= "\n".'   '."\n";
142: $Ptr .= '<input type="submit" name="NextStudent" ';
143: $Ptr .= 'value="Next Student" />'."\n";
144: $Ptr .= '</td></tr></table>'."\n";
145:
146: return $Ptr;
147: }
148:
149: sub CreateTableHeadings {
150: my($cache,$spacing,$infoKeys,$infoHeadings,$sequenceKeys,
151: $sequenceHeadings)=@_;
152:
153: my $Str = '';
154: $Str .= '<br><table border="0" cellpadding="0" cellspacing="0"><tr>'."\n";
155:
156: my $displayString = '<td align="left"><pre><a href="/adm/statistics?';
157: $displayString .= 'sort=LINKDATA">DISPLAYDATA</a>FORMATTING';
158: $displayString .= $spacing.'</pre></td>'."\n";
159: $Str .= &Apache::lonhtmlcommon::CreateHeadings($cache,
160: $infoKeys,
161: $infoHeadings,
162: $displayString,
163: 'preformatted');
164:
165: $displayString = '<td align="left"><pre>DISPLAYDATA'.$spacing;
166: $displayString .= '</pre></td>'."\n";
167: $Str .= &Apache::lonhtmlcommon::CreateHeadings($cache,
168: $sequenceKeys,
169: $sequenceHeadings,
170: $displayString,
171: 'preformatted');
172:
173: $Str .= '<td><pre>Total Solved/Total Problems</pre></td>';
174: $Str .= '</tr></table>'."\n";
175:
176: return $Str;
177: }
178:
179: =pod
180:
181: =item &FormatStudentData()
182:
183: First, FormatStudentInformation is called and prefixes the course information.
184: This function produces a formatted string of the student's course information.
185: Each column of data represents all the problems for a given sequence. For
186: valid grade data, a link is created for that problem to a submission record
187: for that problem.
188:
189: =over 4
190:
191: Input: $name, $studentInformation, $ChartDB
192:
193: $name: The name and domain of the current student in name:domain format
194:
195: $studentInformation: A pointer to an array holding the names used to
196: remove data from the hash. They represent
197: the name of the data to be removed.
198:
199: $ChartDB: The name of the cached data database which will be tied to that
200: database.
201:
202: Output: $Str
203:
204: $Str: Formatted string that is an entire row of the chart. It is a
205: concatenation of student information and student course information.
206:
207: =back
208:
209: =cut
210:
211: sub StudentReport {
212: my ($cache,$name,$spacing,$showSequences)=@_;
213: my ($username,$domain)=split(':',$name);
214:
215: my $Str = '';
216: if($cache->{$name.':error'} =~ /course/) {
217: $Str .= '<b><font color="blue">No course data for student </font>';
218: $Str .= '<font color="red">'.$username.'.</font></b><br>';
219: return $Str;
220: }
221:
222: my $Version;
223: my $problemsCorrect = 0;
224: my $totalProblems = 0;
225: my $problemsSolved = 0;
226: my $numberOfParts = 0;
227: # foreach my $sequence (split(':', $cache->{'orderedSequences'})) {
228: foreach my $sequence (@$showSequences) {
229: my $characterCount=0;
230: foreach my $problemID (split(':', $cache->{$sequence.':problems'})) {
231: my $problem = $cache->{$problemID.':problem'};
232: my $LatestVersion = $cache->{$name.':version:'.$problem};
233:
234: # Output dashes for all the parts of this problem if there
235: # is no version information about the current problem.
236: if(!$LatestVersion) {
237: foreach my $part (split(/\:/,$cache->{$sequence.':'.
238: $problemID.
239: ':parts'})) {
240: $Str .= ' ';
241: $totalProblems++;
242: $characterCount++;
243: }
244: next;
245: }
246:
247: my %partData=undef;
248: # Initialize part data, display skips correctly
249: # Skip refers to when a student made no submissions on that
250: # part/problem.
251: foreach my $part (split(/\:/,$cache->{$sequence.':'.
252: $problemID.
253: ':parts'})) {
254: $partData{$part.':tries'}=0;
255: $partData{$part.':code'}=' ';
256: }
257:
258: # Looping through all the versions of each part, starting with the
259: # oldest version. Basically, it gets the most recent
260: # set of grade data for each part.
261: for(my $Version=1; $Version<=$LatestVersion; $Version++) {
262: foreach my $part (split(/\:/,$cache->{$sequence.':'.
263: $problemID.
264: ':parts'})) {
265:
266: if(!defined($cache->{$name.":$Version:$problem".
267: ":resource.$part.solved"})) {
268: # No grade for this submission, so skip
269: next;
270: }
271:
272: my $tries=0;
273: my $code=' ';
274:
275: $tries = $cache->{$name.':'.$Version.':'.$problem.
276: ':resource.'.$part.'.tries'};
277: $partData{$part.':tries'}=($tries) ? $tries : 0;
278:
279: my $val = $cache->{$name.':'.$Version.':'.$problem.
280: ':resource.'.$part.'.solved'};
281: if ($val eq 'correct_by_student') {$code = '*';}
282: elsif ($val eq 'correct_by_override') {$code = '+';}
283: elsif ($val eq 'incorrect_attempted') {$code = '.';}
284: elsif ($val eq 'incorrect_by_override'){$code = '-';}
285: elsif ($val eq 'excused') {$code = 'x';}
286: elsif ($val eq 'ungraded_attempted') {$code = '#';}
287: else {$code = ' ';}
288: $partData{$part.':code'}=$code;
289: }
290: }
291:
292: # All grades (except for versionless parts) are displayed as links
293: # to their submission record. Loop through all the parts for the
294: # current problem in the correct order and prepare the output links
295: $Str .= '<a href="/adm/grades?symb=';
296: $Str .= &Apache::lonnet::escape($problem);
297: $Str .= '&student='.$username.'&domain='.$domain;
298: $Str .= '&command=submission">';
299: foreach(split(/\:/,$cache->{$sequence.':'.$problemID.
300: ':parts'})) {
301: if($partData{$_.':code'} eq '*') {
302: $problemsCorrect++;
303: if (($partData{$_.':tries'}<10) &&
304: ($partData{$_.':tries'} ne '')) {
305: $partData{$_.':code'}=$partData{$_.':tries'};
306: }
307: } elsif($partData{$_.':code'} eq '+') {
308: $problemsCorrect++;
309: }
310:
311: $Str .= $partData{$_.':code'};
312: $characterCount++;
313:
314: if($partData{$_.':code'} ne 'x') {
315: $totalProblems++;
316: }
317: }
318: $Str.='</a>';
319: }
320:
321: # Output the number of correct answers for the current sequence.
322: # This part takes up 6 character slots, but is formated right
323: # justified.
324: my $spacesNeeded=$cache->{$sequence.':columnWidth'}-$characterCount;
325: $spacesNeeded -= 3;
326: $Str .= (' 'x$spacesNeeded);
327:
328: my $outputProblemsCorrect = sprintf( "%3d", $problemsCorrect );
329: $Str .= '<font color="#007700">'.$outputProblemsCorrect.'</font>';
330: $problemsSolved += $problemsCorrect;
331: $problemsCorrect=0;
332:
333: $Str .= $spacing;
334: }
335:
336: # Output the total correct problems over the total number of problems.
337: # I don't like this type of formatting, but it is a solution. Need
338: # a way to dynamically determine the space requirements.
339: my $outputProblemsSolved = sprintf( "%4d", $problemsSolved );
340: my $outputTotalProblems = sprintf( "%4d", $totalProblems );
341: $Str .= '<font color="#000088">'.$outputProblemsSolved.
342: ' / '.$outputTotalProblems.'</font>';
343:
344: return $Str;
345: }
346:
347: =pod
348:
349: =item &CreateLegend()
350:
351: This function returns a formatted string containing the legend for the
352: chart. The legend describes the symbols used to represent grades for
353: problems.
354:
355: =cut
356:
357: sub CreateLegend {
358: my $Str = "<p><pre>".
359: "1..9: correct by student in 1..9 tries\n".
360: " *: correct by student in more than 9 tries\n".
361: " +: correct by override\n".
362: " -: incorrect by override\n".
363: " .: incorrect attempted\n".
364: " #: ungraded attempted\n".
365: " : not attempted\n".
366: " x: excused".
367: "</pre><p>";
368: return $Str;
369: }
370:
371: =pod
372:
373: =item &CreateColumnSelectionBox()
374:
375: If there are columns not being displayed then this selection box is created
376: with a list of those columns. When selections are made and the page
377: refreshed, the columns will be removed from this box and the column is
378: put back in the chart. If there is no columns to select, no row is added
379: to the interface table.
380:
381: =over 4
382: Input: $CacheData, $headings
383:
384:
385: $CacheData: A pointer to a hash tied to the cached data
386:
387: $headings: An array of the names of the columns for the student information.
388: They are used for displaying which columns are missing.
389:
390: Output: $notThere
391:
392: $notThere: The string contains one row of a table. The first column has the
393: name of the selection box. The second contains the selection box
394: which has a size of four.
395:
396: =back
397:
398: =cut
399:
400: sub CreateColumnSelectionBox {
401: my ($CacheData,$headings)=@_;
402:
403: my $missing=0;
404: my $notThere='<tr><td align="right"><b>Select column to view:</b>';
405: my $name;
406: $notThere .= '<td align="left">';
407: $notThere .= '<select name="ChartReselect" size="4" multiple="true">'."\n";
408:
409: for(my $index=0; $index<(scalar @$headings); $index++) {
410: if(&ShouldShowColumn($CacheData, 'ChartHeading'.$index)) {
411: next;
412: }
413: $name = $headings->[$index];
414: $notThere .= '<option value="ChartHeading'.$index.'">';
415: $notThere .= $name.'</option>'."\n";
416: $missing++;
417: }
418:
419: foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) {
420: if(&ShouldShowColumn($CacheData, 'ChartSequence'.$sequence)) {
421: next;
422: }
423: $name = $CacheData->{$sequence.':title'};
424: $notThere .= '<option value="ChartSequence'.$sequence.'">';
425: $notThere .= $name.'</option>'."\n";
426: $missing++;
427: }
428:
429: if($missing) {
430: $notThere .= '</select>';
431: } else {
432: $notThere='<tr><td>';
433: }
434:
435: return $notThere.'</td></tr>';
436: }
437:
438: =pod
439:
440: =item &CreateColumnSelectors()
441:
442: This function generates the checkboxes above the column headings. The
443: column will be removed if the checkbox is unchecked.
444:
445: =over 4
446:
447: Input: $CacheData, $headings
448:
449: $CacheData: A pointer to a hash tied to the cached data
450:
451: $headings: An array of the names of the columns for the student
452: information. They are used to know what are the student information columns
453:
454: Output: $present
455:
456: $present: The string contains the first row of a table. Each column contains
457: a checkbox which is left justified. Currently left justification is used
458: for consistency of location over the column in which it presides.
459:
460: =back
461:
462: =cut
463:
464: sub CreateColumnSelectors {
465: my ($headings)=@_;
466: =pod
467: my $found=0;
468: my ($name, $length, $position);
469:
470: my $present = '<tr>';
471: for(my $index=0; $index<(scalar @$headings); $index++) {
472: $present .= '<td align="left">';
473: $present .= '<input type="checkbox" checked="on" ';
474: $present .= 'name="ChartHeading'.$index.'" />';
475: $present .= '</td>';
476: $found++;
477: }
478:
479: foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) {
480: if(!&ShouldShowColumn($CacheData, 'ChartSequence'.$sequence)) {
481: next;
482: }
483: $present .= '<td align="left">';
484: $present .= '<input type="checkbox" checked="on" ';
485: $present .= 'name="ChartSequence'.$sequence.'" />';
486: $present .= '</td>';
487: $found++;
488: }
489:
490: if(!$found) {
491: $present = '';
492: }
493:
494: return $present.'<td></td></tr></form>'."\n";;
495: =cut
496: }
497:
498: #---- END Student Assessment Web Page ----------------------------------------
499:
500: #---- Student Assessment Worker Functions ------------------------------------
501:
502: sub FindSelectedStudent {
503: my($cache, $selectedName, $students)=@_;
504: for(my $index=0;
505: ($selectedName ne 'All Students') && ($index<(scalar @$students));
506: $index++) {
507: my $fullname = $cache->{$students->[$index].':fullname'};
508: if($fullname eq $selectedName) {
509: if($cache->{'StudentAssessmentMove'} eq 'next') {
510: if($index == ((scalar @$students) - 1)) {
511: $selectedName = $students->[0];
512: } else {
513: $selectedName = $students->[$index+1];
514: }
515: } elsif($cache->{'StudentAssessmentMove'} eq 'previous') {
516: if($index == 0) {
517: $selectedName = $students->[-1];
518: } else {
519: $selectedName = $students->[$index-1];
520: }
521: } else {
522: $selectedName = $students->[$index];
523: }
524: last;
525: }
526: }
527:
528: return $selectedName;
529: }
530:
531: =pod
532:
533: =item &ShouldShowColumn()
534:
535: Determine if a specified column should be shown on the chart.
536:
537: =over 4
538:
539: Input: $cache, $test
540:
541: $cache: A pointer to the hash tied to the cached data
542:
543: $test: The form name of the column (heading.$headingIndex) or
544: (sequence.$sequenceIndex)
545:
546: Output: 0 (false), 1 (true)
547:
548: =back
549:
550: =cut
551:
552: sub ShouldShowColumns {
553: my ($cache,$headings,$cacheKey)=@_;
554:
555: my @infoKeys=();
556: my @infoHeadings=();
557:
558: my @sequenceKeys=();
559: my @sequenceHeadings=();
560:
561: my $index;
562: for($index=0; $index < scalar @$headings; $index++) {
563: push(@infoHeadings, $headings->[$index]);
564: push(@infoKeys, $cacheKey->[$index]);
565: }
566:
567: foreach my $sequence (split(/\:/,$cache->{'orderedSequences'})) {
568: push(@sequenceHeadings, $cache->{$sequence.':title'});
569: push(@sequenceKeys, $sequence);
570: }
571:
572: # my $headings=$cache->{'form.ChartHeadings'};
573: # my $sequences=$cache->{'form.ChartSequences'};
574: # if($headings eq 'ALLHEADINGS' || $sequences eq 'ALLSEQUENCES' ||
575: # $headings=~/$test/ || $sequences=~/$test/) {
576: # return 1;
577: # }
578:
579: return (\@infoHeadings, \@infoKeys, \@sequenceHeadings,
580: \@sequenceKeys);
581: }
582:
583: #---- END Student Assessment Worker Functions --------------------------------
584:
585: 1;
586: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>