Annotation of loncom/interface/statistics/lonproblemanalysis.pm, revision 1.48
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.48 ! matthew 3: # $Id: lonproblemanalysis.pm,v 1.47 2003/10/30 16:24:36 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: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
1.11 minaeibi 27: package Apache::lonproblemanalysis;
1.1 stredwic 28:
29: use strict;
30: use Apache::lonnet();
1.25 matthew 31: use Apache::loncommon();
1.7 stredwic 32: use Apache::lonhtmlcommon();
1.23 matthew 33: use Apache::loncoursedata();
34: use Apache::lonstatistics;
35: use Apache::lonlocal;
1.37 matthew 36: use HTML::Entities();
1.42 matthew 37: use Time::Local();
1.43 matthew 38: use Spreadsheet::WriteExcel();
1.2 stredwic 39:
1.40 matthew 40: my $plotcolors = ['#33ff00',
41: '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933',
42: '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66',
43: ];
1.39 matthew 44:
1.46 matthew 45: my @SubmitButtons = ({ name => 'ProblemAnalyis',
46: text => 'Analyze Problem Again' },
47: { name => 'ClearCache',
48: text => 'Clear Caches' },
49: { name => 'updatecaches',
50: text => 'Update Student Data' },
51: { name => 'SelectAnother',
52: text => 'Choose a different resource' },
53: { name => 'ExcelOutput',
54: text => 'Produce Excel Output' });
55:
56: sub render_resource {
57: my ($resource) = @_;
58: ##
59: ## Render the problem
60: my $base;
61: ($base,undef) = ($resource->{'src'} =~ m|(.*/)[^/]*$|);
62: $base = "http://".$ENV{'SERVER_NAME'}.$base;
63: my $rendered_problem =
64: &Apache::lonnet::ssi_body($resource->{'src'});
65: $rendered_problem =~ s/<\s*form\s*/<nop /g;
66: $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
67: return '<table bgcolor="ffffff"><tr><td>'.
68: '<base href="'.$base.'" />'.
69: $rendered_problem.
70: '</td></tr></table>';
71: }
72:
1.1 stredwic 73: sub BuildProblemAnalysisPage {
1.23 matthew 74: my ($r,$c)=@_;
1.48 ! matthew 75: #
1.24 matthew 76: $r->print('<h2>'.&mt('Option Response Problem Analysis').'</h2>');
1.25 matthew 77: $r->print(&CreateInterface());
1.28 matthew 78: #
79: my @Students = @Apache::lonstatistics::Students;
80: #
1.46 matthew 81: if (@Students < 1) {
82: $r->print('<h2>There are no students in the sections selected</h2>');
83: }
84: #
85: &Apache::loncoursedata::clear_internal_caches();
1.41 matthew 86: if (exists($ENV{'form.ClearCache'}) ||
87: exists($ENV{'form.updatecaches'}) ||
1.33 matthew 88: (exists($ENV{'form.firstanalysis'}) &&
89: $ENV{'form.firstanalysis'} ne 'no')) {
90: &Apache::lonstatistics::Gather_Full_Student_Data($r);
91: }
92: if (! exists($ENV{'form.firstanalysis'})) {
93: $r->print('<input type="hidden" name="firstanalysis" value="yes" />');
94: } else {
95: $r->print('<input type="hidden" name="firstanalysis" value="no" />');
96: }
1.39 matthew 97: $r->rflush();
1.46 matthew 98: #
1.33 matthew 99: if (exists($ENV{'form.problemchoice'}) &&
100: ! exists($ENV{'form.SelectAnother'})) {
1.46 matthew 101: foreach my $button (@SubmitButtons) {
102: $r->print('<input type="submit" name="'.$button->{'name'}.'" '.
103: 'value="'.&mt($button->{'text'}).'" />');
104: $r->print(' 'x5);
105: }
1.31 matthew 106: $r->print('<input type="hidden" name="problemchoice" value="'.
107: $ENV{'form.problemchoice'}.'" />');
108: #
1.25 matthew 109: $r->print('<hr />');
1.23 matthew 110: #
1.47 matthew 111: my ($symb,$part,$resid,$resptype) = &get_problem_symb(
1.46 matthew 112: &Apache::lonnet::unescape($ENV{'form.problemchoice'}));
1.43 matthew 113: $r->rflush();
1.28 matthew 114: #
1.23 matthew 115: my $resource = &get_resource_from_symb($symb);
1.47 matthew 116: if (! defined($resource) || ! defined($resptype)) {
1.46 matthew 117: $r->print('resource is undefined');
118: } else {
1.44 matthew 119: $r->print('<h1>'.$resource->{'title'}.'</h1>');
1.41 matthew 120: $r->print('<h3>'.$resource->{'src'}.'</h3>');
1.47 matthew 121: $r->print(&render_resource($resource));
1.44 matthew 122: $r->rflush();
1.48 ! matthew 123: my %Data = &get_problem_data($resource->{'src'});
! 124: my $ProblemData = $Data{$part.'.'.$resid};
1.47 matthew 125: if ($resptype eq 'option') {
126: &OptionResponseAnalysis($r,$resource,$resid,$ProblemData,
127: \@Students);
128: } elsif ($resptype eq 'radiobutton') {
1.48 ! matthew 129: &RadioResponseAnalysis($r,$resource,$part,$resid,$ProblemData,
1.47 matthew 130: \@Students);
131: } else {
132: $r->print('<h2>This analysis is not supported</h2>');
133: }
1.7 stredwic 134: }
1.23 matthew 135: $r->print('<hr />');
1.25 matthew 136: } else {
1.31 matthew 137: $r->print('<input type="submit" name="ProblemAnalysis" value="'.
138: &mt('Analyze Problem').'" />');
139: $r->print(' 'x5);
1.27 matthew 140: $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
1.46 matthew 141: $r->print(&ProblemSelector());
1.1 stredwic 142: }
143: }
144:
1.48 ! matthew 145: =pod
! 146:
! 147: Removed code:
1.47 matthew 148:
149: #########################################################
150: #########################################################
151: ##
152: ## Radio Response Routines
153: ##
154: #########################################################
155: #########################################################
156: sub RadioResponseAnalysis {
1.48 ! matthew 157: my ($r,$resource,$part,$respid,$ProblemData,$Students) = @_;
! 158: my $analysis_html;
1.47 matthew 159: my $PerformanceData =
160: &Apache::loncoursedata::get_response_data
1.48 ! matthew 161: ($Students,$resource->{'symb'},$respid);
! 162: if (! defined($PerformanceData) ||
! 163: ref($PerformanceData) ne 'ARRAY' ) {
! 164: $analysis_html = '<h2>'.
! 165: &mt('There is no submission data for this resource').
! 166: '</h2>';
! 167: $r->print($analysis_html);
1.47 matthew 168: return;
169: }
1.48 ! matthew 170: if (exists($ENV{'form.ExcelOutput'})) {
! 171: $analysis_html .= &RR_Excel_output($r,$resource,$PerformanceData,
! 172: $ProblemData);
! 173: } elsif ($ENV{'form.AnalyzeOver'} eq 'Tries') {
! 174: $analysis_html .= &RR_Tries_Analysis($r,$resource,$PerformanceData,
! 175: $ProblemData);
! 176: } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
! 177: $analysis_html .= &RR_Time_Analysis($r,$resource,$PerformanceData,
! 178: $ProblemData);
! 179: } else {
! 180: $analysis_html .= '<h2>'.
! 181: &mt('The analysis you have selected is not supported at this time').
! 182: '</h2>';
! 183: }
! 184: $r->print($analysis_html);
! 185: }
! 186:
! 187:
! 188: sub RR_Excel_output {
! 189: my ($r,$PerformanceData,$ProblemData) = @_;
! 190: return '<h1>No!</h1>';
! 191: }
! 192:
! 193: sub RR_Tries_Analysis {
! 194: my ($r,$resource,$PerformanceData,$ProblemData) = @_;
! 195: my $analysis_html;
! 196: my $mintries = 1;
! 197: my $maxtries = $ENV{'form.NumPlots'};
! 198: my ($table,$Foils,$Concepts) = &build_foil_index($ProblemData);
! 199: if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
! 200: $table = '<h3>'.
! 201: &mt('Not enough data for concept analysis. '.
! 202: 'Performing Foil Analysis').
! 203: '</h3>'.$table;
! 204: $ENV{'form.AnalyzeAs'} = 'Foils';
! 205: }
! 206: $analysis_html .= $table;
! 207: my @TryData = &RR_tries_data_analysis($r,$PerformanceData);
! 208: if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
! 209: $analysis_html = &RR_Tries_Foil_Analysis($mintries,$maxtries,$Foils,
! 210: \@TryData,$ProblemData);
! 211: } else {
! 212: $analysis_html = &RR_Tries_Concept_Analysis($mintries,$maxtries,
! 213: $Concepts,
! 214: \@TryData,
! 215: $ProblemData);
! 216: }
! 217: return $analysis_html;
! 218: }
! 219:
! 220: sub RR_tries_data_analysis {
! 221: my ($r,$Attempt_data) = @_;
! 222: my @TryData;
! 223: foreach my $attempt (@$Attempt_data) {
! 224: my %Attempt = &hashify_attempt($attempt);
! 225: my ($answer,undef) = split('=',$Attempt{'submission'});
! 226: $TryData[$Attempt{'tries'}]->{$answer}++;
! 227: }
! 228: return @TryData;
! 229: }
! 230:
! 231: sub RR_Time_Analysis {
! 232: my ($r,$PerformanceData,$ProblemData) = @_;
! 233: my $html;
! 234: return $html;
! 235: }
! 236:
! 237: sub RR_Tries_Foil_Analysis {
! 238: my ($min,$max,$Foils,$TryData,$ProblemData) = @_;
! 239: my $html;
! 240: #
! 241: # Compute the data neccessary to make the plots
! 242: for (my $try=$min;$try<=$max;$try++) {
! 243: my @PlotData_Correct;
! 244: my @PlotData_Incorrect;
! 245: next if ($try > scalar(@{$TryData}));
! 246: next if (! defined($TryData->[$try-1]));
! 247: my %DataSet = %{$TryData->[$try-1]};
! 248: my $total = 0;
! 249: foreach my $foilid (@$Foils) {
! 250: $total += $DataSet{$foilid};
! 251: }
! 252: foreach my $foilid (@$Foils) {
! 253: if ($total == 0) {
! 254: push (@PlotData_Correct,0);
! 255: push (@PlotData_Incorrect,0);
! 256: } else {
! 257: if ($ProblemData->{'_Foils'}->{$foilid}->{'value'} eq 'true') {
! 258: push (@PlotData_Correct,
! 259: int(100*$DataSet{$foilid}/$total));
! 260: push (@PlotData_Incorrect,0);
! 261: } else {
! 262: push (@PlotData_Correct,0);
! 263: push (@PlotData_Incorrect,
! 264: int(100*$DataSet{$foilid}/$total));
! 265: }
! 266: }
! 267: }
! 268: my $title='Attempt '.$try;
! 269: my $xlabel = $total.' Submissions';
! 270: $html.= &Apache::loncommon::DrawBarGraph($title,
! 271: $xlabel,
! 272: 'Percent Choosing',
! 273: 100,
! 274: ['#33ff00','#ff3300'],
! 275: \@PlotData_Correct,
! 276: \@PlotData_Incorrect);
! 277: }
! 278: &Apache::lonnet::logthis('plot = '.$html);
! 279: return $html;
! 280: }
! 281:
! 282: sub RR_Tries_Concept_Analysis {
! 283: my ($min,$max,$Concepts,$ResponseData,$ProblemData) = @_;
! 284: my $html;
! 285: return $html;
! 286: }
! 287:
! 288: sub RR_Time_Foil_Analysis {
! 289: my ($min,$max,$Foils,$ResponseData,$ProblemData) = @_;
! 290: my $html;
! 291: return $html;
! 292: }
! 293:
! 294: sub RR_Time_Concept_Analysis {
! 295: my ($min,$max,$Concepts,$ResponseData,$ProblemData) = @_;
! 296: my $html;
! 297: return $html;
! 298: }
! 299:
! 300:
! 301:
! 302: sub get_Radio_problem_data {
! 303: my ($url) = @_;
! 304: my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
! 305: (my $garbage,$Answ)=split('_HASH_REF__',$Answ,2);
! 306: my %Answer = &Apache::lonnet::str2hash($Answ);
! 307: my %Partdata;
! 308: &Apache::lonnet::logthis('url = '.$url);
! 309: foreach my $part (@{$Answer{'parts'}}) {
! 310: while (my($key,$value) = each(%Answer)) {
! 311: # if (ref($value) eq 'ARRAY') {
! 312: # &Apache::lonnet::logthis('is ref part:'.$part.' '.$key.'='.join(',',@$value));
! 313: # } else {
! 314: # &Apache::lonnet::logthis('notref part:'.$part.' '.$key.'='.$value);
! 315: # }
! 316: next if ($key !~ /^$part/);
! 317: $key =~ s/^$part\.//;
! 318: if ($key eq 'foils') {
! 319: $Partdata{$part}->{'_Foils'}=$value;
! 320: } elsif ($key eq 'options') {
! 321: $Partdata{$part}->{'_Options'}=$value;
! 322: } elsif ($key eq 'shown') {
! 323: $Partdata{$part}->{'_Shown'}=$value;
! 324: } elsif ($key =~ /^foil.value.(.*)$/) {
! 325: $Partdata{$part}->{$1}->{'value'}=$value;
! 326: } elsif ($key =~ /^foil.text.(.*)$/) {
! 327: $Partdata{$part}->{$1}->{'text'}=$value;
! 328: }
! 329: }
! 330: }
! 331: return %Partdata;
1.47 matthew 332: }
1.48 ! matthew 333:
! 334: =cut
! 335:
! 336:
1.33 matthew 337: #########################################################
338: #########################################################
339: ##
1.46 matthew 340: ## Option Response Routines
1.33 matthew 341: ##
342: #########################################################
343: #########################################################
1.46 matthew 344: sub OptionResponseAnalysis {
345: my ($r,$resource,$resid,$ProblemData,$Students) = @_;
346: my $PerformanceData =
1.47 matthew 347: &Apache::loncoursedata::get_response_data
1.46 matthew 348: ($Students,$resource->{'symb'},$resid);
349: if (! defined($PerformanceData) ||
350: ref($PerformanceData) ne 'ARRAY' ) {
351: $r->print('<h2>'.
352: &mt('There is no student data for this problem.').
353: '</h2>');
354: } else {
355: $r->rflush();
356: if (exists($ENV{'form.ExcelOutput'})) {
357: my $result = &prepare_optionresponse_excel_sheet($r,$resource,
358: $PerformanceData,
359: $ProblemData);
360: $r->print($result);
361: $r->rflush();
1.39 matthew 362: } else {
1.46 matthew 363: if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
364: my $analysis_html = &tries_analysis($r,
365: $PerformanceData,
366: $ProblemData);
367: $r->print($analysis_html);
368: $r->rflush();
369: } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
370: my $analysis_html = &time_analysis($PerformanceData,
371: $ProblemData);
372: $r->print($analysis_html);
373: $r->rflush();
1.39 matthew 374: } else {
1.46 matthew 375: $r->print('<h2>'.
376: &mt('The analysis you have selected is '.
377: 'not supported at this time').
378: '</h2>');
379: }
1.36 matthew 380: }
1.39 matthew 381: }
382: }
383:
1.33 matthew 384: #########################################################
1.46 matthew 385: #
386: # Option Response: Tries Analysis
387: #
1.33 matthew 388: #########################################################
389: sub tries_analysis {
1.43 matthew 390: my ($r,$PerformanceData,$ORdata) = @_;
1.33 matthew 391: my $mintries = 1;
392: my $maxtries = $ENV{'form.NumPlots'};
1.39 matthew 393: my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
394: if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
395: $table = '<h3>'.
396: &mt('Not enough data for concept analysis. '.
397: 'Performing Foil Analysis').
398: '</h3>'.$table;
399: $ENV{'form.AnalyzeAs'} = 'Foils';
400: }
1.43 matthew 401: my %ResponseData = &analyze_option_data_by_tries($r,$PerformanceData,
1.36 matthew 402: $mintries,$maxtries);
1.42 matthew 403: my $analysis = '';
404: if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
405: $analysis = &Tries_Foil_Analysis($mintries,$maxtries,$Foils,
406: \%ResponseData,$ORdata);
407: } else {
408: $analysis = &Tries_Concept_Analysis($mintries,$maxtries,
409: $Concepts,\%ResponseData,$ORdata);
410: }
411: $table .= $analysis;
412: return $table;
413: }
414:
415: sub Tries_Foil_Analysis {
416: my ($mintries,$maxtries,$Foils,$respdat,$ORdata) = @_;
417: my %ResponseData = %$respdat;
1.31 matthew 418: #
419: # Compute the data neccessary to make the plots
1.42 matthew 420: my @PlotData;
421: foreach my $foilid (@$Foils) {
422: for (my $i=$mintries;$i<=$maxtries;$i++) {
423: if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
424: push(@{$PlotData[$i]->{'_correct'}},0);
425: } else {
426: push(@{$PlotData[$i]->{'_correct'}},
427: 100*$ResponseData{$foilid}->[$i]->{'_correct'}/
428: $ResponseData{$foilid}->[$i]->{'_total'});
429: }
1.48 ! matthew 430: foreach my $option (@{$ORdata->{'_Options'}}) {
1.42 matthew 431: push(@{$PlotData[$i]->{'_total'}},
432: $ResponseData{$foilid}->[$i]->{'_total'});
433: if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
434: push (@{$PlotData[$i]->{$option}},0);
435: } else {
436: if ($ResponseData{$foilid}->[$i]->{'_total'} ==
437: $ResponseData{$foilid}->[$i]->{'_correct'}) {
438: push(@{$PlotData[$i]->{$option}},0);
1.39 matthew 439: } else {
440: push (@{$PlotData[$i]->{$option}},
441: 100 * $ResponseData{$foilid}->[$i]->{$option} /
1.42 matthew 442: ($ResponseData{$foilid}->[$i]->{'_total'} -
443: $ResponseData{$foilid}->[$i]->{'_correct'}));
1.39 matthew 444: }
1.36 matthew 445: }
446: }
447: }
1.42 matthew 448: }
449: #
450: # Build a table for the plots
451: my $analysis_html = "<table>\n";
452: my $foilkey = &build_option_index($ORdata);
453: for (my $i=$mintries;$i<=$maxtries;$i++) {
454: my $count = $ResponseData{'_total'}->[$i];
455: if ($count == 0) {
456: $count = 'no submissions';
457: } elsif ($count == 1) {
458: $count = '1 submission';
459: } else {
460: $count = $count.' submissions';
461: }
462: my $title = 'Attempt '.$i.', '.$count;
463: my @Datasets;
1.48 ! matthew 464: foreach my $option ('_correct',@{$ORdata->{'_Options'}}) {
1.42 matthew 465: next if (! exists($PlotData[$i]->{$option}));
466: push(@Datasets,$PlotData[$i]->{$option});
467: }
1.46 matthew 468: my $correctgraph = &Apache::loncommon::DrawBarGraph
1.42 matthew 469: ($title,'Foil Number','Percent Correct',
470: 100,$plotcolors,$Datasets[0]);
471: $analysis_html.= '<tr><td>'.$correctgraph.'</td>';
472: ##
473: ##
474: for (my $i=0; $i< scalar(@{$Datasets[0]});$i++) {
475: $Datasets[0]->[$i]=0;
476: }
477: $count = $ResponseData{'_total'}->[$i]-$ResponseData{'_correct'}->[$i];
478: if ($count == 0) {
479: $count = 'no submissions';
480: } elsif ($count == 1) {
481: $count = '1 submission';
482: } else {
483: $count = $count.' submissions';
484: }
485: $title = 'Attempt '.$i.', '.$count;
1.46 matthew 486: my $incorrectgraph = &Apache::loncommon::DrawBarGraph
1.42 matthew 487: ($title,'Foil Number','% Option Chosen Incorrectly',
488: 100,$plotcolors,@Datasets);
489: $analysis_html.= '<td>'.$incorrectgraph.'</td>';
490: $analysis_html.= '<td>'.$foilkey."<td></tr>\n";
491: }
492: $analysis_html .= "</table>\n";
493: return $analysis_html;
494: }
495:
496: sub Tries_Concept_Analysis {
497: my ($mintries,$maxtries,$Concepts,$respdat,$ORdata) = @_;
498: my %ResponseData = %$respdat;
499: my $analysis_html = "<table>\n";
500: #
501: # Compute the data neccessary to make the plots
502: my @PlotData;
503: # Concept analysis
504: #
505: # Note: we do not bother with characterizing the students incorrect
506: # answers at the concept level because an incorrect answer for one foil
507: # may be a correct answer for another foil.
508: my %ConceptData;
509: foreach my $concept (@{$Concepts}) {
510: for (my $i=$mintries;$i<=$maxtries;$i++) {
511: #
512: # Gather the per-attempt data
513: my $cdata = $ConceptData{$concept}->[$i];
514: foreach my $foilid (@{$concept->{'foils'}}) {
515: $cdata->{'_correct'} +=
516: $ResponseData{$foilid}->[$i]->{'_correct'};
517: $cdata->{'_total'} +=
518: $ResponseData{$foilid}->[$i]->{'_total'};
519: }
520: push (@{$PlotData[$i]->{'_total'}},$cdata->{'_total'});
521: if ($cdata->{'_total'} == 0) {
522: push (@{$PlotData[$i]->{'_correct'}},0);
523: } else {
524: push (@{$PlotData[$i]->{'_correct'}},
525: 100*$cdata->{'_correct'}/$cdata->{'_total'});
1.36 matthew 526: }
1.25 matthew 527: }
1.42 matthew 528: }
1.31 matthew 529: # Build a table for the plots
1.25 matthew 530: for (my $i=$mintries;$i<=$maxtries;$i++) {
1.39 matthew 531: my $minstu = $PlotData[$i]->{'_total'}->[0];
532: my $maxstu = $PlotData[$i]->{'_total'}->[0];
533: foreach my $count (@{$PlotData[$i]->{'_total'}}) {
1.36 matthew 534: if ($minstu > $count) {
535: $minstu = $count;
1.27 matthew 536: }
1.36 matthew 537: if ($maxstu < $count) {
538: $maxstu = $count;
1.27 matthew 539: }
540: }
1.39 matthew 541: $maxstu = 0 if (! defined($maxstu));
542: $minstu = 0 if (! defined($minstu));
1.35 matthew 543: my $title;
1.42 matthew 544: my $count = $ResponseData{'_total'}->[$i];
545: if ($count == 0) {
546: $count = 'no submissions';
547: } elsif ($count == 1) {
548: $count = '1 submission';
1.27 matthew 549: } else {
1.42 matthew 550: $count = $count.' submissions';
1.39 matthew 551: }
1.42 matthew 552: $title = 'Attempt '.$i.', '.$count;
1.46 matthew 553: my $graphlink = &Apache::loncommon::DrawBarGraph
1.42 matthew 554: ($title,'Concept Number','Percent Correct',
555: 100,$plotcolors,$PlotData[$i]->{'_correct'});
556: $analysis_html.= '<tr><td>'.$graphlink."</td></tr>\n";
1.25 matthew 557: }
1.42 matthew 558: $analysis_html .= "</table>\n";
559: return $analysis_html;
1.25 matthew 560: }
561:
562: sub analyze_option_data_by_tries {
1.43 matthew 563: my ($r,$PerformanceData,$mintries,$maxtries) = @_;
1.25 matthew 564: my %Trydata;
565: $mintries = 1 if (! defined($mintries) || $mintries < 1);
566: $maxtries = $mintries if (! defined($maxtries) || $maxtries < $mintries);
1.26 matthew 567: foreach my $row (@$PerformanceData) {
568: next if (! defined($row));
1.47 matthew 569: my $tries = &get_tries_from_row($row);
1.46 matthew 570: my %Row = &Process_OR_Row($row);
1.43 matthew 571: next if (! %Row);
1.42 matthew 572: while (my ($foilid,$href) = each(%Row)) {
573: if (! ref($href)) {
574: $Trydata{$foilid}->[$tries] += $href;
575: next;
576: }
577: while (my ($option,$value) = each(%$href)) {
578: $Trydata{$foilid}->[$tries]->{$option}+=$value;
1.25 matthew 579: }
580: }
581: }
582: return %Trydata;
583: }
584:
1.33 matthew 585: #########################################################
1.46 matthew 586: #
587: # Option Response: Time Analysis
588: #
1.33 matthew 589: #########################################################
590: sub time_analysis {
591: my ($PerformanceData,$ORdata) = @_;
1.42 matthew 592: my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
593: if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
594: $table = '<h3>'.
595: &mt('Not enough data for concept analysis. '.
596: 'Performing Foil Analysis').
597: '</h3>'.$table;
598: $ENV{'form.AnalyzeAs'} = 'Foils';
599: }
1.33 matthew 600: my $num_plots = $ENV{'form.NumPlots'};
601: my $num_data = scalar(@$PerformanceData)-1;
602: my $percent = sprintf('%2f',100/$num_plots);
1.42 matthew 603: #
1.37 matthew 604: $table .= "<table>\n";
1.33 matthew 605: for (my $i=0;$i<$num_plots;$i++) {
1.42 matthew 606: ##
1.34 matthew 607: my $starttime = &Apache::lonhtmlcommon::get_date_from_form
608: ('startdate_'.$i);
609: my $endtime = &Apache::lonhtmlcommon::get_date_from_form
610: ('enddate_'.$i);
611: if (! defined($starttime) || ! defined($endtime)) {
1.42 matthew 612: my $sec_in_day = 86400;
1.47 matthew 613: my $last_sub_time = &get_time_from_row($PerformanceData->[-1]);
1.42 matthew 614: my ($sday,$smon,$syear);
615: (undef,undef,undef,$sday,$smon,$syear) =
616: localtime($last_sub_time - $sec_in_day*$i);
617: $starttime = &Time::Local::timelocal(0,0,0,$sday,$smon,$syear);
618: $endtime = $starttime + $sec_in_day;
619: if ($i == ($num_plots -1 )) {
1.47 matthew 620: $starttime = &get_time_from_row($PerformanceData->[0]);
1.42 matthew 621: }
622: }
1.34 matthew 623: my $startdateform = &Apache::lonhtmlcommon::date_setter
624: ('Statistics','startdate_'.$i,$starttime);
625: my $enddateform = &Apache::lonhtmlcommon::date_setter
626: ('Statistics','enddate_'.$i,$endtime);
1.42 matthew 627: #
628: my $begin_index;
629: my $end_index;
630: my $j;
631: while (++$j < scalar(@$PerformanceData)) {
1.47 matthew 632: last if (&get_time_from_row($PerformanceData->[$j])
1.46 matthew 633: > $starttime);
1.42 matthew 634: }
635: $begin_index = $j;
636: while (++$j < scalar(@$PerformanceData)) {
1.47 matthew 637: last if (&get_time_from_row($PerformanceData->[$j]) > $endtime);
1.42 matthew 638: }
639: $end_index = $j;
640: ##
641: my $interval = {
642: begin_index => $begin_index,
643: end_index => $end_index,
644: startdateform => $startdateform,
645: enddateform => $enddateform,
646: };
647: if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
648: $table .= &Foil_Time_Analysis($PerformanceData,$ORdata,$Foils,
649: $interval);
650: } else {
651: $table .= &Concept_Time_Analysis($PerformanceData,$ORdata,
652: $Concepts,$interval);
653: }
1.33 matthew 654: }
1.42 matthew 655: #
1.33 matthew 656: return $table;
657: }
658:
1.42 matthew 659: sub Foil_Time_Analysis {
660: my ($PerformanceData,$ORdata,$Foils,$interval) = @_;
661: my $analysis_html;
662: my $foilkey = &build_option_index($ORdata);
663: my ($begin_index,$end_index) = ($interval->{'begin_index'},
664: $interval->{'end_index'});
1.33 matthew 665: my %TimeData;
666: #
667: # Compute the number getting the foils correct or incorrects
1.42 matthew 668: for (my $j=$begin_index;$j<=$end_index;$j++) {
669: my $row = $PerformanceData->[$j];
1.33 matthew 670: next if (! defined($row));
1.46 matthew 671: my %Row = &Process_OR_Row($row);
1.42 matthew 672: while (my ($foilid,$href) = each(%Row)) {
673: if (! ref($href)) {
674: $TimeData{$foilid} += $href;
675: next;
676: }
677: while (my ($option,$value) = each(%$href)) {
678: $TimeData{$foilid}->{$option}+=$value;
1.33 matthew 679: }
680: }
681: }
1.39 matthew 682: my @Plotdata;
1.42 matthew 683: foreach my $foil (@$Foils) {
684: my $total = $TimeData{$foil}->{'_total'};
685: if ($total == 0) {
686: push(@{$Plotdata[0]},0);
687: } else {
688: push(@{$Plotdata[0]},
689: 100 * $TimeData{$foil}->{'_correct'} / $total);
690: }
691: my $total_incorrect = $total - $TimeData{$foil}->{'_correct'};
692: my $optionidx = 1;
1.48 ! matthew 693: foreach my $option (@{$ORdata->{'_Options'}}) {
1.42 matthew 694: if ($total_incorrect == 0) {
695: push(@{$Plotdata[$optionidx]},0);
696: } else {
697: push(@{$Plotdata[$optionidx]},
698: 100 * $TimeData{$foil}->{$option} / $total_incorrect);
1.37 matthew 699: }
1.42 matthew 700: } continue {
701: $optionidx++;
1.39 matthew 702: }
1.42 matthew 703: }
704: #
705: # Create the plot
706: my $count = $end_index-$begin_index;
707: my $title;
708: if ($count == 0) {
709: $title = 'no submissions';
710: } elsif ($count == 1) {
711: $title = 'one submission';
1.39 matthew 712: } else {
1.42 matthew 713: $title = $count.' submissions';
714: }
1.46 matthew 715: my $correctplot = &Apache::loncommon::DrawBarGraph($title,
716: 'Foil Number',
717: 'Percent Correct',
718: 100,
719: $plotcolors,
720: $Plotdata[0]);
1.42 matthew 721: for (my $j=0; $j< scalar(@{$Plotdata[0]});$j++) {
722: $Plotdata[0]->[$j]=0;
723: }
724: $count = $end_index-$begin_index-$TimeData{'_correct'};
725: if ($count == 0) {
726: $title = 'no submissions';
727: } elsif ($count == 1) {
728: $title = 'one submission';
729: } else {
730: $title = $count.' submissions';
731: }
1.46 matthew 732: my $incorrectplot = &Apache::loncommon::DrawBarGraph($title,
1.42 matthew 733: 'Foil Number',
734: 'Incorrect Option Choice',
735: 100,
736: $plotcolors,
737: @Plotdata);
738: $analysis_html.='<tr>'.
739: '<td>'.$correctplot.'</td>'.
740: '<td>'.$incorrectplot.'</td>'.
741: '<td align="left" valign="top">'.$foilkey.'</td>'."</tr>\n";
742: $analysis_html.= '<tr>'.'<td colspan="3">'.
743: '<b>Start Time</b>:'.
744: ' '.$interval->{'startdateform'}.'<br />'.
745: '<b>End Time</b> : '.
746: ' '.$interval->{'enddateform'}.'<br />'.
747: # '<b>Plot Title</b> :'.
748: # (" "x3).$interval->{'titleform'}.
749: '</td>'.
750: "</tr>\n";
751: return $analysis_html;
752: }
753:
754: sub Concept_Time_Analysis {
755: my ($PerformanceData,$ORdata,$Concepts,$interval) = @_;
756: my $analysis_html;
757: ##
758: ## Determine starttime, endtime, startindex, endindex
759: my ($begin_index,$end_index) = ($interval->{'begin_index'},
760: $interval->{'end_index'});
761: my %TimeData;
762: #
763: # Compute the number getting the foils correct or incorrects
764: for (my $j=$begin_index;$j<=$end_index;$j++) {
765: my $row = $PerformanceData->[$j];
766: next if (! defined($row));
1.46 matthew 767: my %Row = &Process_OR_Row($row);
1.42 matthew 768: while (my ($foilid,$href) = each(%Row)) {
769: if (! ref($href)) {
770: $TimeData{$foilid} += $href;
771: next;
1.37 matthew 772: }
1.42 matthew 773: while (my ($option,$value) = each(%$href)) {
774: $TimeData{$foilid}->{$option}+=$value;
1.37 matthew 775: }
1.33 matthew 776: }
777: }
778: #
1.42 matthew 779: # Put the data in plottable form
780: my @Plotdata;
781: foreach my $concept (@$Concepts) {
782: my ($total,$correct);
783: foreach my $foil (@{$concept->{'foils'}}) {
784: $total += $TimeData{$foil}->{'_total'};
785: $correct += $TimeData{$foil}->{'_correct'};
786: }
787: if ($total == 0) {
788: push(@Plotdata,0);
789: } else {
790: push(@Plotdata,100 * $correct / $total);
791: }
792: }
793: #
1.33 matthew 794: # Create the plot
1.42 matthew 795: my $title = ($end_index - $begin_index).' submissions';
1.46 matthew 796: my $correctplot = &Apache::loncommon::DrawBarGraph($title,
1.42 matthew 797: 'Concept Number',
798: 'Percent Correct',
799: 100,
800: $plotcolors,
801: \@Plotdata);
802: $analysis_html.='<tr>'.
803: '<td>'.$correctplot.'</td>'.
804: '<td align="left" valign="top">'.
805: '<b>Start Time</b>: '.$interval->{'startdateform'}.'<br />'.
806: '<b>End Time</b> : '.
807: ' '.$interval->{'enddateform'}.'<br />'.
808: # '<b>Plot Title</b> :'.(" "x3).
809: # $interval->{'titleform'}.
810: '</td>'.
811: "</tr>\n";
812: return $analysis_html;
813: }
814:
815: #########################################################
816: #########################################################
817: ##
818: ## Excel output
819: ##
820: #########################################################
821: #########################################################
1.46 matthew 822: sub prepare_optionresponse_excel_sheet {
1.43 matthew 823: my ($r,$resource,$PerformanceData,$ORdata) = @_;
1.44 matthew 824: my $response = '';
1.42 matthew 825: my (undef,$Foils,$Concepts) = &build_foil_index($ORdata);
1.43 matthew 826: #
827: # Create excel worksheet
828: my $filename = '/prtspool/'.
829: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
830: time.'_'.rand(1000000000).'.xls';
831: my $workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
832: if (! defined($workbook)) {
833: $r->log_error("Error creating excel spreadsheet $filename: $!");
834: $r->print('<p>'.&mt("Unable to create new Excel file. ".
835: "This error has been logged. ".
836: "Please alert your LON-CAPA administrator").
837: '</p>');
838: return undef;
839: }
840: #
841: $workbook->set_tempdir('/home/httpd/perl/tmp');
842: #
843: # Define some potentially useful formats
844: my $format;
845: $format->{'header'} = $workbook->add_format(bold => 1,
846: bottom => 1,
847: align => 'center');
848: $format->{'bold'} = $workbook->add_format(bold=>1);
849: $format->{'h1'} = $workbook->add_format(bold=>1, size=>18);
850: $format->{'h2'} = $workbook->add_format(bold=>1, size=>16);
851: $format->{'h3'} = $workbook->add_format(bold=>1, size=>14);
852: $format->{'date'} = $workbook->add_format(num_format=>
853: 'mmm d yyyy hh:mm AM/PM');
854: #
855: # Create and populate main worksheets
856: my $problem_data_sheet = $workbook->addworksheet('Problem Data');
857: my $student_data_sheet = $workbook->addworksheet('Student Data');
858: my $response_data_sheet = $workbook->addworksheet('Response Data');
859: foreach my $sheet ($problem_data_sheet,$student_data_sheet,
860: $response_data_sheet) {
861: $sheet->write(0,0,$resource->{'title'},$format->{'h2'});
862: $sheet->write(1,0,$resource->{'src'},$format->{'h3'});
863: }
864: #
865: my $result;
866: $result = &build_problem_data_worksheet($problem_data_sheet,$format,
867: $Concepts,$ORdata);
868: if ($result ne 'okay') {
869: # Do something useful
870: }
871: $result = &build_student_data_worksheet($student_data_sheet,$format);
872: if ($result ne 'okay') {
873: # Do something useful
874: }
875: $result = &build_response_data_worksheet($response_data_sheet,$format,
876: $PerformanceData,$Foils,
877: $ORdata);
878: if ($result ne 'okay') {
879: # Do something useful
880: }
1.44 matthew 881: $response_data_sheet->activate();
1.43 matthew 882: #
883: # Close the excel file
884: $workbook->close();
885: #
886: # Write a link to allow them to download it
1.44 matthew 887: $result .= '<h2>'.&mt('Excel Raw Data Output').'</h2>'.
888: '<p><a href="'.$filename.'">'.
889: &mt('Your Excel spreadsheet.').
890: '</a></p>'."\n";
1.43 matthew 891: return $result;
892: }
893:
894: sub build_problem_data_worksheet {
895: my ($worksheet,$format,$Concepts,$ORdata) = @_;
896: my $rows_output = 3;
897: my $cols_output = 0;
898: $worksheet->write($rows_output++,0,'Problem Structure',$format->{'h3'});
899: ##
900: ##
901: my @Headers;
902: if (@$Concepts > 1) {
903: @Headers = ("Concept\nNumber",'Concept',"Foil\nNumber",
904: 'Foil Name','Foil Text','Correct value');
905: } else {
906: @Headers = ('Foil Number','FoilName','Foil Text','Correct value');
907: }
908: $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
1.48 ! matthew 909: my %Foildata = %{$ORdata->{'_Foils'}};
1.43 matthew 910: my $conceptindex = 1;
911: my $foilindex = 1;
912: foreach my $concept (@$Concepts) {
913: my @FoilsInConcept = @{$concept->{'foils'}};
914: my $firstfoil = shift(@FoilsInConcept);
915: if (@$Concepts > 1) {
916: $worksheet->write_row($rows_output++,0,
917: [$conceptindex,
918: $concept->{'name'},
919: $foilindex++,
920: $Foildata{$firstfoil}->{'name'},
921: $Foildata{$firstfoil}->{'text'},
922: $Foildata{$firstfoil}->{'value'},]);
923: } else {
924: $worksheet->write_row($rows_output++,0,
925: [ $foilindex++,
926: $Foildata{$firstfoil}->{'name'},
927: $Foildata{$firstfoil}->{'text'},
928: $Foildata{$firstfoil}->{'value'},]);
929: }
930: foreach my $foilid (@FoilsInConcept) {
931: if (@$Concepts > 1) {
932: $worksheet->write_row($rows_output++,0,
933: ['',
934: '',
935: $foilindex,
936: $Foildata{$foilid}->{'name'},
937: $Foildata{$foilid}->{'text'},
938: $Foildata{$foilid}->{'value'},]);
939: } else {
940: $worksheet->write_row($rows_output++,0,
941: [$foilindex,
942: $Foildata{$foilid}->{'name'},
943: $Foildata{$foilid}->{'text'},
944: $Foildata{$foilid}->{'value'},]);
945: }
946: } continue {
947: $foilindex++;
948: }
949: } continue {
950: $conceptindex++;
951: }
952: $rows_output++;
953: $rows_output++;
954: ##
955: ## Option data output
956: $worksheet->write($rows_output++,0,'Options',$format->{'header'});
1.48 ! matthew 957: foreach my $string (@{$ORdata->{'_Options'}}) {
1.43 matthew 958: $worksheet->write($rows_output++,0,$string);
959: }
960: return 'okay';
961: }
962:
963: sub build_student_data_worksheet {
964: my ($worksheet,$format) = @_;
965: my $rows_output = 3;
966: my $cols_output = 0;
967: $worksheet->write($rows_output++,0,'Student Data',$format->{'h3'});
968: my @Headers = ('full name','username','domain','section',
969: "student\nnumber",'identifier');
970: $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
971: my @Students = @Apache::lonstatistics::Students;
972: my $studentrows = &Apache::loncoursedata::get_student_data(\@Students);
973: my %ids;
974: foreach my $row (@$studentrows) {
975: my ($mysqlid,$student) = @$row;
976: $ids{$student}=$mysqlid;
977: }
978: foreach my $student (@Students) {
979: my $name_domain = $student->{'username'}.':'.$student->{'domain'};
980: $worksheet->write_row($rows_output++,0,
981: [$student->{'fullname'},
982: $student->{'username'},$student->{'domain'},
983: $student->{'section'},$student->{'id'},
984: $ids{$name_domain}]);
985: }
986: return;
987: }
988:
989: sub build_response_data_worksheet {
990: my ($worksheet,$format,$PerformanceData,$Foils,$ORdata)=@_;
991: my $rows_output = 3;
992: my $cols_output = 0;
993: $worksheet->write($rows_output++,0,'Response Data',$format->{'h3'});
994: $worksheet->set_column(1,1,20);
995: $worksheet->set_column(2,2,13);
996: my @Headers = ('identifier','time','award detail','attempt');
997: foreach my $foil (@$Foils) {
998: push (@Headers,$foil.' submission');
999: push (@Headers,$foil.' grading');
1000: }
1001: $worksheet->write_row($rows_output++,0,\@Headers,$format->{'header'});
1002: #
1003: foreach my $row (@$PerformanceData) {
1004: next if (! defined($row));
1005: my ($student,$award,$grading,$submission,$time,$tries) = @$row;
1006: my @Foilgrades = split('&',$grading);
1007: my @Foilsubs = split('&',$submission);
1008: my %ResponseData;
1009: for (my $j=0;$j<=$#Foilgrades;$j++) {
1010: my ($foilid,$correct) = split('=',$Foilgrades[$j]);
1011: my (undef,$submission) = split('=',$Foilsubs[$j]);
1012: $submission = &Apache::lonnet::unescape($submission);
1013: $ResponseData{$foilid.' submission'}=$submission;
1014: $ResponseData{$foilid.' award'}=$correct;
1015: }
1016: $worksheet->write($rows_output,$cols_output++,$student);
1017: $worksheet->write($rows_output,$cols_output++,
1018: &calc_serial($time),$format->{'date'});
1019: $worksheet->write($rows_output,$cols_output++,$award);
1020: $worksheet->write($rows_output,$cols_output++,$tries);
1021: foreach my $foilid (@$Foils) {
1022: $worksheet->write($rows_output,$cols_output++,
1023: $ResponseData{$foilid.' submission'});
1024: $worksheet->write($rows_output,$cols_output++,
1025: $ResponseData{$foilid.' award'});
1026: }
1027: $rows_output++;
1028: $cols_output = 0;
1029: }
1030: return;
1.42 matthew 1031: }
1032:
1.43 matthew 1033:
1034: ##
1035: ## The following is copied from datecalc1.pl, part of the
1036: ## Spreadsheet::WriteExcel CPAN module.
1037: ##
1038: ##
1039: ######################################################################
1040: #
1041: # Demonstration of writing date/time cells to Excel spreadsheets,
1042: # using UNIX/Perl time as source of date/time.
1043: #
1044: # Copyright 2000, Andrew Benham, adsb@bigfoot.com
1045: #
1046: ######################################################################
1047: #
1048: # UNIX/Perl time is the time since the Epoch (00:00:00 GMT, 1 Jan 1970)
1049: # measured in seconds.
1050: #
1051: # An Excel file can use exactly one of two different date/time systems.
1052: # In these systems, a floating point number represents the number of days
1053: # (and fractional parts of the day) since a start point. The floating point
1054: # number is referred to as a 'serial'.
1055: # The two systems ('1900' and '1904') use different starting points:
1056: # '1900'; '1.00' is 1 Jan 1900 BUT 1900 is erroneously regarded as
1057: # a leap year - see:
1058: # http://support.microsoft.com/support/kb/articles/Q181/3/70.asp
1059: # for the excuse^H^H^H^H^H^Hreason.
1060: # '1904'; '1.00' is 2 Jan 1904.
1061: #
1062: # The '1904' system is the default for Apple Macs. Windows versions of
1063: # Excel have the option to use the '1904' system.
1064: #
1065: # Note that Visual Basic's "DateSerial" function does NOT erroneously
1066: # regard 1900 as a leap year, and thus its serials do not agree with
1067: # the 1900 serials of Excel for dates before 1 Mar 1900.
1068: #
1069: # Note that StarOffice (at least at version 5.2) does NOT erroneously
1070: # regard 1900 as a leap year, and thus its serials do not agree with
1071: # the 1900 serials of Excel for dates before 1 Mar 1900.
1072: #
1073: ######################################################################
1074: #
1075: # Calculation description
1076: # =======================
1077: #
1078: # 1900 system
1079: # -----------
1080: # Unix time is '0' at 00:00:00 GMT 1 Jan 1970, i.e. 70 years after 1 Jan 1900.
1081: # Of those 70 years, 17 (1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68)
1082: # were leap years with an extra day.
1083: # Thus there were 17 + 70*365 days = 25567 days between 1 Jan 1900 and
1084: # 1 Jan 1970.
1085: # In the 1900 system, '1' is 1 Jan 1900, but as 1900 was not a leap year
1086: # 1 Jan 1900 should really be '2', so 1 Jan 1970 is '25569'.
1087: #
1088: # 1904 system
1089: # -----------
1090: # Unix time is '0' at 00:00:00 GMT 1 Jan 1970, i.e. 66 years after 1 Jan 1904.
1091: # Of those 66 years, 17 (1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68)
1092: # were leap years with an extra day.
1093: # Thus there were 17 + 66*365 days = 24107 days between 1 Jan 1904 and
1094: # 1 Jan 1970.
1095: # In the 1904 system, 2 Jan 1904 being '1', 1 Jan 1970 is '24107'.
1096: #
1097: ######################################################################
1098: #
1099: # Copyright (c) 2000, Andrew Benham.
1100: # This program is free software. It may be used, redistributed and/or
1101: # modified under the same terms as Perl itself.
1102: #
1103: # Andrew Benham, adsb@bigfoot.com
1104: # London, United Kingdom
1105: # 11 Nov 2000
1106: #
1107: ######################################################################
1108:
1109: # Use 1900 date system on all platforms other than Apple Mac (for which
1110: # use 1904 date system).
1111: my $DATE_SYSTEM = ($^O eq 'MacOS') ? 1 : 0;
1112:
1113: #-----------------------------------------------------------
1114: # calc_serial()
1115: #
1116: # Called with (up to) 2 parameters.
1117: # 1. Unix timestamp. If omitted, uses current time.
1118: # 2. GMT flag. Set to '1' to return serial in GMT.
1119: # If omitted, returns serial in appropriate timezone.
1120: #
1121: # Returns date/time serial according to $DATE_SYSTEM selected
1122: #-----------------------------------------------------------
1123: sub calc_serial {
1124: my $time = (defined $_[0]) ? $_[0] : time();
1125: my $gmtflag = (defined $_[1]) ? $_[1] : 0;
1126:
1127: # Divide timestamp by number of seconds in a day.
1128: # This gives a date serial with '0' on 1 Jan 1970.
1129: my $serial = $time / 86400;
1130:
1131: # Adjust the date serial by the offset appropriate to the
1132: # currently selected system (1900/1904).
1133: if ($DATE_SYSTEM == 0) { # use 1900 system
1134: $serial += 25569;
1135: } else { # use 1904 system
1136: $serial += 24107;
1137: }
1138:
1139: unless ($gmtflag) {
1140: # Now have a 'raw' serial with the right offset. But this
1141: # gives a serial in GMT, which is false unless the timezone
1142: # is GMT. We need to adjust the serial by the appropriate
1143: # timezone offset.
1144: # Calculate the appropriate timezone offset by seeing what
1145: # the differences between localtime and gmtime for the given
1146: # time are.
1147:
1148: my @gmtime = gmtime($time);
1149: my @ltime = localtime($time);
1150:
1151: # For the first 7 elements of the two arrays, adjust the
1152: # date serial where the elements differ.
1153: for (0 .. 6) {
1154: my $diff = $ltime[$_] - $gmtime[$_];
1155: if ($diff) {
1156: $serial += _adjustment($diff,$_);
1157: }
1158: }
1159: }
1160:
1161: # Perpetuate the error that 1900 was a leap year by decrementing
1162: # the serial if we're using the 1900 system and the date is prior to
1163: # 1 Mar 1900. This has the effect of making serial value '60'
1164: # 29 Feb 1900.
1165:
1166: # This fix only has any effect if UNIX/Perl time on the platform
1167: # can represent 1900. Many can't.
1168:
1169: unless ($DATE_SYSTEM) {
1170: $serial-- if ($serial < 61); # '61' is 1 Mar 1900
1171: }
1172: return $serial;
1173: }
1174:
1175: sub _adjustment {
1176: # Based on the difference in the localtime/gmtime array elements
1177: # number, return the adjustment required to the serial.
1178:
1179: # We only look at some elements of the localtime/gmtime arrays:
1180: # seconds unlikely to be different as all known timezones
1181: # have an offset of integral multiples of 15 minutes,
1182: # but it's easy to do.
1183: # minutes will be different for timezone offsets which are
1184: # not an exact number of hours.
1185: # hours very likely to be different.
1186: # weekday will differ when localtime/gmtime difference
1187: # straddles midnight.
1188: #
1189: # Assume that difference between localtime and gmtime is less than
1190: # 5 days, then don't have to do maths for day of month, month number,
1191: # year number, etc...
1192:
1193: my ($delta,$element) = @_;
1194: my $adjust = 0;
1195:
1196: if ($element == 0) { # Seconds
1197: $adjust = $delta/86400; # 60 * 60 * 24
1198: } elsif ($element == 1) { # Minutes
1199: $adjust = $delta/1440; # 60 * 24
1200: } elsif ($element == 2) { # Hours
1201: $adjust = $delta/24; # 24
1202: } elsif ($element == 6) { # Day of week number
1203: # Catch difference straddling Sat/Sun in either direction
1204: $delta += 7 if ($delta < -4);
1205: $delta -= 7 if ($delta > 4);
1206:
1207: $adjust = $delta;
1208: }
1209: return $adjust;
1.1 stredwic 1210: }
1211:
1.46 matthew 1212: sub build_foil_index {
1213: my ($ORdata) = @_;
1.48 ! matthew 1214: return if (! exists($ORdata->{'_Foils'}));
! 1215: my %Foildata = %{$ORdata->{'_Foils'}};
1.46 matthew 1216: my @Foils = sort(keys(%Foildata));
1217: my %Concepts;
1218: foreach my $foilid (@Foils) {
1.48 ! matthew 1219: push(@{$Concepts{$Foildata{$foilid}->{'_Concept'}}},
1.46 matthew 1220: $foilid);
1221: }
1222: undef(@Foils);
1223: # Having gathered the concept information in a hash, we now translate it
1224: # into an array because we need to be consistent about order.
1225: # Also put the foils in order, too.
1226: my $sortfunction = sub {
1227: my %Numbers = (one => 1,
1228: two => 2,
1229: three => 3,
1230: four => 4,
1231: five => 5,
1232: six => 6,
1233: seven => 7,
1234: eight => 8,
1235: nine => 9,
1236: ten => 10,);
1237: my $a1 = lc($a);
1238: my $b1 = lc($b);
1239: if (exists($Numbers{$a})) {
1240: $a1 = $Numbers{$a};
1241: }
1242: if (exists($Numbers{$b})) {
1243: $b1 = $Numbers{$b};
1244: }
1245: $a1 cmp $b1;
1246: };
1247: my @Concepts;
1248: foreach my $concept (sort $sortfunction (keys(%Concepts))) {
1249: push(@Concepts,{ name => $concept,
1250: foils => [@{$Concepts{$concept}}]});
1251: push(@Foils,(@{$Concepts{$concept}}));
1252: }
1253: #
1254: # Build up the table of row labels.
1255: my $table = '<table border="1" >'."\n";
1256: if (@Concepts > 1) {
1257: $table .= '<tr>'.
1258: '<th>'.&mt('Concept Number').'</th>'.
1259: '<th>'.&mt('Concept').'</th>'.
1260: '<th>'.&mt('Foil Number').'</th>'.
1261: '<th>'.&mt('Foil Name').'</th>'.
1262: '<th>'.&mt('Foil Text').'</th>'.
1263: '<th>'.&mt('Correct Value').'</th>'.
1264: "</tr>\n";
1265: } else {
1266: $table .= '<tr>'.
1267: '<th>'.&mt('Foil Number').'</th>'.
1268: '<th>'.&mt('Foil Name').'</th>'.
1269: '<th>'.&mt('Foil Text').'</th>'.
1270: '<th>'.&mt('Correct Value').'</th>'.
1271: "</tr>\n";
1272: }
1273: my $conceptindex = 1;
1274: my $foilindex = 1;
1275: foreach my $concept (@Concepts) {
1276: my @FoilsInConcept = @{$concept->{'foils'}};
1277: my $firstfoil = shift(@FoilsInConcept);
1278: if (@Concepts > 1) {
1279: $table .= '<tr>'.
1280: '<td>'.$conceptindex.'</td>'.
1281: '<td>'.$concept->{'name'}.'</td>'.
1282: '<td>'.$foilindex++.'</td>'.
1283: '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
1284: '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
1285: '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
1286: "</tr>\n";
1287: } else {
1288: $table .= '<tr>'.
1289: '<td>'.$foilindex++.'</td>'.
1290: '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
1291: '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
1292: '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
1293: "</tr>\n";
1294: }
1295: foreach my $foilid (@FoilsInConcept) {
1296: if (@Concepts > 1) {
1297: $table .= '<tr>'.
1298: '<td></td>'.
1299: '<td></td>'.
1300: '<td>'.$foilindex.'</td>'.
1301: '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
1302: '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
1303: '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
1304: "</tr>\n";
1305: } else {
1306: $table .= '<tr>'.
1307: '<td>'.$foilindex.'</td>'.
1308: '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
1309: '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
1310: '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
1311: "</tr>\n";
1312: }
1313: } continue {
1314: $foilindex++;
1315: }
1316: } continue {
1317: $conceptindex++;
1318: }
1319: $table .= "</table>\n";
1320: #
1321: # Build option index with color stuff
1322: return ($table,\@Foils,\@Concepts);
1323: }
1324:
1325: sub build_option_index {
1326: my ($ORdata)= @_;
1327: my $table = "<table>\n";
1328: my $optionindex = 0;
1329: my @Rows;
1.48 ! matthew 1330: foreach my $option (&mt('correct option chosen'),@{$ORdata->{'_Options'}}) {
1.46 matthew 1331: push (@Rows,
1332: '<tr>'.
1333: '<td bgcolor="'.$plotcolors->[$optionindex++].'">'.
1334: (' 'x4).'</td>'.
1335: '<td>'.$option.'</td>'.
1336: "</tr>\n");
1337: }
1338: shift(@Rows); # Throw away 'correct option chosen' color
1339: $table .= join('',reverse(@Rows));
1340: $table .= "</table>\n";
1341: }
1342:
1.33 matthew 1343: #########################################################
1344: #########################################################
1345: ##
1.46 matthew 1346: ## Generic Interface Routines
1.33 matthew 1347: ##
1348: #########################################################
1349: #########################################################
1.23 matthew 1350: sub CreateInterface {
1.28 matthew 1351: ##
1352: ## Environment variable initialization
1.36 matthew 1353: if (! exists$ENV{'form.AnalyzeOver'}) {
1354: $ENV{'form.AnalyzeOver'} = 'Tries';
1.28 matthew 1355: }
1356: ##
1357: ## Build the menu
1.7 stredwic 1358: my $Str = '';
1.23 matthew 1359: $Str .= '<table cellspacing="5">'."\n";
1360: $Str .= '<tr>';
1361: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
1362: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
1.31 matthew 1363: # $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
1364: $Str .= '<td align="center"> </td>';
1.23 matthew 1365: $Str .= '</tr>'."\n";
1.31 matthew 1366: ##
1367: ##
1.23 matthew 1368: $Str .= '<tr><td align="center">'."\n";
1369: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.28 matthew 1370: $Str .= '</td>';
1371: #
1372: $Str .= '<td align="center">';
1.23 matthew 1373: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.28 matthew 1374: $Str .= '</td>';
1375: #
1.31 matthew 1376: # $Str .= '<td align="center">';
1.23 matthew 1377: my $only_seq_with_assessments = sub {
1378: my $s=shift;
1379: if ($s->{'num_assess'} < 1) {
1380: return 0;
1381: } else {
1382: return 1;
1383: }
1384: };
1.31 matthew 1385: &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
1.23 matthew 1386: $only_seq_with_assessments);
1.36 matthew 1387: ##
1388: ##
1.28 matthew 1389: $Str .= '<td>';
1.36 matthew 1390: { # These braces are here to organize the code, not scope it.
1391: {
1392: $Str .= '<nobr>'.&mt('Analyze Over ');
1393: $Str .='<select name="AnalyzeOver" >';
1394: $Str .= '<option value="Tries" ';
1395: if (! exists($ENV{'form.AnalyzeOver'}) ||
1396: $ENV{'form.AnalyzeOver'} eq 'Tries'){
1397: # Default to Tries
1398: $Str .= ' selected ';
1399: }
1400: $Str .= '>'.&mt('Tries').'</option>';
1401: $Str .= '<option value="Time" ';
1402: $Str .= ' selected ' if ($ENV{'form.AnalyzeOver'} eq 'Time');
1403: $Str .= '>'.&mt('Time').'</option>';
1404: $Str .= '</select></nobr><br />';
1405: }
1406: {
1407: $Str .= '<nobr>'.&mt('Analyze as ');
1408: $Str .='<select name="AnalyzeAs" >';
1409: $Str .= '<option value="Concepts" ';
1410: if (! exists($ENV{'form.AnalyzeAs'}) ||
1411: $ENV{'form.AnalyzeAs'} eq 'Concepts'){
1412: # Default to Concepts
1413: $Str .= ' selected ';
1414: }
1415: $Str .= '>'.&mt('Concepts').'</option>';
1416: $Str .= '<option value="Foils" ';
1417: $Str .= ' selected ' if ($ENV{'form.AnalyzeAs'} eq 'Foils');
1418: $Str .= '>'.&mt('Foils').'</option>';
1419: $Str .= '</select></nobr><br />';
1420: }
1421: {
1422: $Str .= '<br /><nobr>'.&mt('Number of Plots:');
1423: $Str .= '<select name="NumPlots">';
1424: if (! exists($ENV{'form.NumPlots'})
1425: || $ENV{'form.NumPlots'} < 1
1426: || $ENV{'form.NumPlots'} > 20) {
1427: $ENV{'form.NumPlots'} = 5;
1428: }
1429: foreach my $i (1,2,3,4,5,6,7,8,10,15,20) {
1430: $Str .= '<option value="'.$i.'" ';
1431: if ($ENV{'form.NumPlots'} == $i) { $Str.=' selected '; }
1432: $Str .= '>'.$i.'</option>';
1433: }
1434: $Str .= '</select></nobr>';
1435: }
1.28 matthew 1436: }
1437: $Str .= '</td>';
1.36 matthew 1438: ##
1439: ##
1.28 matthew 1440: $Str .= '</tr>'."\n";
1.23 matthew 1441: $Str .= '</table>'."\n";
1.42 matthew 1442: return $Str;
1.23 matthew 1443: }
1444:
1.46 matthew 1445: sub ProblemSelector {
1.23 matthew 1446: my $Str;
1447: $Str = "\n<table>\n";
1448: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1449: next if ($seq->{'num_assess'}<1);
1450: my $seq_str = '';
1451: foreach my $res (@{$seq->{'contents'}}) {
1.26 matthew 1452: next if ($res->{'type'} ne 'assessment');
1.23 matthew 1453: foreach my $part (@{$res->{'parts'}}) {
1454: my $partdata = $res->{'partdata'}->{$part};
1.47 matthew 1455: # &Apache::lonnet::logthis('----------------');
1456: # while (my ($k,$v)=each(%$partdata)) {
1457: # if (ref($v) eq 'ARRAY') {
1458: # &Apache::lonnet::logthis($k.' = '.join(',',@$v));
1459: # } else {
1460: # &Apache::lonnet::logthis($k.' = '.$v);
1461: # }
1462: # }
1463: if ((! exists($partdata->{'option'}) ||
1464: $partdata->{'option'} == 0 ) &&
1465: (! exists($partdata->{'radiobutton'}) ||
1466: $partdata->{'radiobutton'} == 0)) {
1.23 matthew 1467: next;
1468: }
1469: for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){
1470: my $respid = $partdata->{'ResponseIds'}->[$i];
1471: my $resptype = $partdata->{'ResponseTypes'}->[$i];
1.48 ! matthew 1472: if ($resptype eq 'option' ){
! 1473: # if ($resptype eq 'option' || $resptype eq 'radiobutton') {
! 1474: my $value =
! 1475: &Apache::lonnet::escape($res->{'symb'}.':'.$part.
! 1476: ':'.$respid.':'.$resptype);
1.23 matthew 1477: my $checked = '';
1478: if ($ENV{'form.problemchoice'} eq $value) {
1479: $checked = 'checked ';
1480: }
1481: $seq_str .= '<tr><td>'.
1482: '<input type="radio" name="problemchoice" value="'.$value.'" '.$checked.'/>'.
1483: '</td><td>'.
1.48 ! matthew 1484: '<a href="'.$res->{'src'}.'">'.$res->{'title'}.'</a> ';
! 1485: # '<a href="'.$res->{'src'}.'">'.$resptype.' '.$res->{'title'}.'</a> ';
1.23 matthew 1486: if ($partdata->{'option'} > 1) {
1487: $seq_str .= &mt('response').' '.$respid;
1488: }
1489: $seq_str .= "</td></tr>\n";
1.11 minaeibi 1490: }
1491: }
1492: }
1493: }
1.23 matthew 1494: if ($seq_str ne '') {
1495: $Str .= '<tr><td> </td><td><b>'.$seq->{'title'}.'</b></td>'.
1496: "</tr>\n".$seq_str;
1497: }
1.11 minaeibi 1498: }
1.23 matthew 1499: $Str .= "</table>\n";
1500: return $Str;
1.33 matthew 1501: }
1502:
1503: #########################################################
1504: #########################################################
1505: ##
1506: ## Misc functions
1507: ##
1508: #########################################################
1509: #########################################################
1510: sub get_problem_symb {
1511: my $problemstring = shift();
1.47 matthew 1512: my ($symb,$partid,$resid,$resptype) =
1513: ($problemstring=~ /^(.*):([^:]*):([^:]*):([^:]*)$/);
1514: return ($symb,$partid,$resid,$resptype);
1.11 minaeibi 1515: }
1516:
1.23 matthew 1517: sub get_resource_from_symb {
1518: my ($symb) = @_;
1519: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1520: foreach my $res (@{$seq->{'contents'}}) {
1521: if ($res->{'symb'} eq $symb) {
1522: return $res;
1.2 stredwic 1523: }
1.1 stredwic 1524: }
1525: }
1.23 matthew 1526: return undef;
1.42 matthew 1527: }
1528:
1.47 matthew 1529:
1530: #########################################################
1531: #########################################################
1532: ##
1533: ## Misc Option Response functions
1534: ##
1535: #########################################################
1536: #########################################################
1537: sub get_time_from_row {
1.42 matthew 1538: my ($row) = @_;
1539: if (ref($row)) {
1.47 matthew 1540: return $row->[&Apache::loncoursedata::RD_timestamp()];
1.42 matthew 1541: }
1542: return undef;
1543: }
1544:
1.47 matthew 1545: sub get_tries_from_row {
1.42 matthew 1546: my ($row) = @_;
1547: if (ref($row)) {
1.47 matthew 1548: return $row->[&Apache::loncoursedata::RD_tries()];
1.42 matthew 1549: }
1550: return undef;
1551: }
1552:
1.48 ! matthew 1553: sub hashify_attempt {
! 1554: my ($row) = @_;
! 1555: my %attempt;
! 1556: $attempt{'tries'} = $row->[&Apache::loncoursedata::RD_tries()];
! 1557: $attempt{'submission'} = $row->[&Apache::loncoursedata::RD_submission()];
! 1558: $attempt{'award'} = $row->[&Apache::loncoursedata::RD_awarddetail()];
! 1559: $attempt{'timestamp'} = $row->[&Apache::loncoursedata::RD_timestamp()];
! 1560: return %attempt;
! 1561: }
! 1562:
1.46 matthew 1563: sub Process_OR_Row {
1.42 matthew 1564: my ($row) = @_;
1565: my %RowData;
1.47 matthew 1566: my $student_id = $row->[&Apache::loncoursedata::RD_student_id()];
1567: my $award = $row->[&Apache::loncoursedata::RD_awarddetail()];
1568: my $grading = $row->[&Apache::loncoursedata::RD_response_eval()];
1569: my $submission = $row->[&Apache::loncoursedata::RD_submission()];
1570: my $time = $row->[&Apache::loncoursedata::RD_timestamp()];
1571: my $tries = $row->[&Apache::loncoursedata::RD_tries()];
1.43 matthew 1572: return undef if ($award eq 'MISSING_ANSWER');
1.42 matthew 1573: if ($award =~ /(APPROX_ANS|EXACT_ANS)/) {
1574: $RowData{'_correct'} = 1;
1575: }
1576: $RowData{'_total'} = 1;
1577: my @Foilgrades = split('&',$grading);
1578: my @Foilsubs = split('&',$submission);
1579: for (my $j=0;$j<=$#Foilgrades;$j++) {
1580: my ($foilid,$correct) = split('=',$Foilgrades[$j]);
1581: my (undef,$submission) = split('=',$Foilsubs[$j]);
1582: if ($correct) {
1583: $RowData{$foilid}->{'_correct'}++;
1584: } else {
1585: $submission = &Apache::lonnet::unescape($submission);
1586: $RowData{$foilid}->{$submission}++;
1587: }
1588: $RowData{$foilid}->{'_total'}++;
1589: }
1590: return %RowData;
1.1 stredwic 1591: }
1592:
1.39 matthew 1593: ##
1594: ## get problem data and put it into a useful data structure.
1595: ## note: we must force each foil and option to not begin or end with
1596: ## spaces as they are stored without such data.
1597: ##
1.48 ! matthew 1598: sub get_problem_data {
1.25 matthew 1599: my ($url) = @_;
1600: my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
1.23 matthew 1601: (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);
1.25 matthew 1602: my %Answer;
1.23 matthew 1603: %Answer=&Apache::lonnet::str2hash($Answ);
1.25 matthew 1604: my %Partdata;
1605: foreach my $part (@{$Answer{'parts'}}) {
1606: while (my($key,$value) = each(%Answer)) {
1607: next if ($key !~ /^$part/);
1608: $key =~ s/^$part\.//;
1609: if (ref($value) eq 'ARRAY') {
1610: if ($key eq 'options') {
1.48 ! matthew 1611: $Partdata{$part}->{'_Options'}=$value;
1.25 matthew 1612: } elsif ($key eq 'concepts') {
1.48 ! matthew 1613: $Partdata{$part}->{'_Concepts'}=$value;
1.25 matthew 1614: } elsif ($key =~ /^concept\.(.*)$/) {
1615: my $concept = $1;
1616: foreach my $foil (@$value) {
1.48 ! matthew 1617: $Partdata{$part}->{'_Foils'}->{$foil}->{'_Concept'}=
1.36 matthew 1618: $concept;
1.25 matthew 1619: }
1620: }
1621: } else {
1622: if ($key=~ /^foil\.text\.(.*)$/) {
1623: my $foil = $1;
1.48 ! matthew 1624: $Partdata{$part}->{'_Foils'}->{$foil}->{'name'}=$foil;
1.43 matthew 1625: $value =~ s/(\s*$|^\s*)//g;
1.48 ! matthew 1626: $Partdata{$part}->{'_Foils'}->{$foil}->{'text'}=$value;
1.25 matthew 1627: } elsif ($key =~ /^foil\.value\.(.*)$/) {
1628: my $foil = $1;
1.48 ! matthew 1629: $Partdata{$part}->{'_Foils'}->{$foil}->{'value'}=$value;
1.25 matthew 1630: }
1.47 matthew 1631: }
1632: }
1633: }
1634: return %Partdata;
1635: }
1636:
1.23 matthew 1637: 1;
1.1 stredwic 1638:
1639: __END__
1.48 ! matthew 1640:
! 1641: #####
! 1642: # partdata{part}->{_Foils}->{foilid}->{'name'} = $
! 1643: # ->{'text'} = $
! 1644: # ->{'value'} = $
! 1645: # ->{'_Concept'} = $
! 1646: # partdata{part}->{_Options} = @
! 1647: # partdata{part}->{_Concepts} = @
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>