File:  [LON-CAPA] / loncom / interface / statistics / lonproblemanalysis.pm
Revision 1.57: download - view: text, annotated - select for diffs
Mon Jan 19 16:33:16 2004 UTC (20 years, 5 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
1. Changed most if not all option response related subroutines to begin
with 'OR'.
2. Placed code to list the type of the problem before it's chosen.  This will
be removed (or reworked) soon anyways.

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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>