--- loncom/interface/statistics/lonproblemanalysis.pm 2002/08/14 20:42:49 1.8 +++ loncom/interface/statistics/lonproblemanalysis.pm 2003/09/29 21:09:27 1.23 @@ -1,7 +1,6 @@ # The LearningOnline Network with CAPA -# (Publication Handler # -# $Id: lonproblemanalysis.pm,v 1.8 2002/08/14 20:42:49 stredwic Exp $ +# $Id: lonproblemanalysis.pm,v 1.23 2003/09/29 21:09:27 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,505 +25,202 @@ # http://www.lon-capa.org/ # # (Navigate problems for statistical reports -# YEAR=2001 -# 5/5,7/9,7/25/1,8/11,9/13,9/26,10/5,10/9,10/22,10/26 Behrouz Minaei -# 11/1,11/4,11/16,12/14,12/16,12/18,12/20,12/31 Behrouz Minaei # YEAR=2002 -# 1/22,2/1,2/6,2/25,3/2,3/6,3/17,3/21,3/22,3/26,4/7,5/6 Behrouz Minaei -# 5/12,5/14,5/15,5/19,5/26,7/16 Behrouz Minaei +# 5/12,7/26,9/7,11/22 Behrouz Minaei # ### -package Apache::lonproblemanalysis; +package Apache::lonproblemanalysis; use strict; use Apache::lonnet(); use Apache::lonhtmlcommon(); -use GDBM_File; - -my $jr; +use Apache::loncoursedata(); +use Apache::lonstatistics; +use Apache::lonlocal; sub BuildProblemAnalysisPage { - my ($cacheDB, $r)=@_; - - my %cache; - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { - $r->print('Unable to tie database.'); - return; - } - - my $Ptr = ''; - $Ptr .= '<table border="0"><tbody>'; - $Ptr .= '<tr><td align="right"><b>Select Sections</b>'; - $Ptr .= '</td>'."\n"; - $Ptr .= '<td align="left">'."\n"; - my @sectionsSelected = split(':',$cache{'sectionsSelected'}); - my @sections = split(':',$cache{'sectionList'}); - $Ptr .= &Apache::lonhtmlcommon::MultipleSectionSelect(\@sections, - \@sectionsSelected, - 'Statistics'); - $Ptr .= '</td></tr>'."\n"; - $Ptr .= '<tr><td align="right"><b>Intervals</b></td>'."\n"; - $Ptr .= '<td align="left">'; - $Ptr .= &IntervalOptions($cache{'Interval'}); - $Ptr .= '</td></tr></table><br>'; - $r->print($Ptr); - $r->rflush(); - $r->print(&OptionResponseTable($cache{'OptionResponses'}, \%cache)); - - untie(%cache); - - return; -} - -sub BuildAnalyzePage { - my ($cacheDB, $students, $courseID,$r)=@_; - - $jr = $r; - my $c = $r->connection; - - my $Str = '</form>'; - my %cache; - - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { - $Str .= 'Unable to tie database.'; - $r->print($Str); - return; - } - - # Remove students who don't have the proper section. - my @sectionsSelected = split(':',$cache{'sectionsSelected'}); - for(my $studentIndex=((scalar @$students)-1); $studentIndex>=0; - $studentIndex--) { - my $value = $cache{$students->[$studentIndex].':section'}; - my $found = 0; - foreach (@sectionsSelected) { - if($_ eq 'none') { - if($value eq '' || !defined($value) || $value eq ' ') { - $found = 1; - last; - } - } else { - if($value eq $_) { - $found = 1; - last; - } - } - } - if($found == 0) { - splice(@$students, $studentIndex, 1); + my ($r,$c)=@_; + $r->print(&mt('<h2>Option Response Problem Analysis</h2>')); + if (exists($ENV{'form.problemchoice'})) { + # This is me getting around my own cleverness: + &Apache::lonstatistics::MapSelect('Maps','multiple,all',5, + undef); + # + my ($symb,$id) = &get_problem_symb( + &Apache::lonnet::unescape($ENV{'form.problemchoice'}) + ); + $r->print('<hr />'); + my $resource = &get_resource_from_symb($symb); + if (defined($resource)) { + $r->print('<table bgcolor="ffffff"><tr><td>'. + # Oh this is dumb! Need to rewrite relative links + # otherwise images (for example) will not show. + &Apache::lonnet::ssi_body($resource->{'src'}). + '</td></tr></table>'); + } else { + $r->print('resource is undefined'); } - } - untie(%cache); - - if(&Apache::loncoursedata::DownloadStudentCourseDataSeparate($students, - 'true', - $cacheDB, - 'true', - 'true', - $courseID, - $r, - $c) ne 'OK') { - $r->print($Str); - return; - } - - - unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { - $Str .= 'Unable to tie database.'; - $r->print($Str); - return; - } - - my ($problemId, $part, $responseId)=split(':',$cache{'AnalyzeInfo'}); - my $uri = $cache{$problemId.':source'}; - my $problem = $cache{$problemId.':problem'}; - my $title = $cache{$problemId.':title'}; - my $interval = $cache{'Interval'}; - - my %ConceptData; - $ConceptData{"Interval"} = $interval; - - #Initialize the option response true answers - my ($analyzeData) = &InitAnalysis($uri, $part, $responseId, $problem, - $students->[0], $courseID); - if(defined($analyzeData->{'error'})) { - $Str .= $analyzeData->{'error'}.'<br>Incorrect part requested.<br>'; - $r->print($Str); + $r->print('<ol>'); + $r->print("<li />render problem\n"); + $r->print("<li />image tag for plot\n"); + $r->print("<li />plot key\n"); + $r->print('</ol>'); + $r->print("<pre>\nProblem choice = $symb $id\n</pre>\n"); + $r->print('<hr />'); + } + $r->print('<input type="hidden" name="analysisfirstcall" value="no" />'); + $r->print(&CreateInterface()); + if (! exists($ENV{'form.analysisfirstcall'})) { return; } - - $r->print($Str); - $Str = ''; - if($c->aborted()) { untie(%cache); return; } - - #compute the intervals - &Interval($part, $problem, $interval, $analyzeData->{'concepts'}, - \%ConceptData); - - $title =~ s/\ /"_"/eg; - $Str .= '<br><b>'.$uri.'</b>'; - - $r->print($Str); - $Str = ''; - if($c->aborted()) { untie(%cache); return; } - - #Java script Progress window - for(my $index=0; $index<(scalar @$students); $index++) { - if($c->aborted()) { untie(%cache); return; } - &OpStatus($problemId, $students->[$index], \%ConceptData, - $analyzeData->{'foil_to_concept'}, $analyzeData, \%cache); - } - - $Str .= '<br>'; - for (my $k=0; $k<$interval; $k++ ) { - if($c->aborted()) { untie(%cache); return $Str; } - $Str .= &DrawGraph($k, $title, $analyzeData->{'concepts'}, - \%ConceptData); - $r->print($Str); - $Str = ''; - } - for (my $k=0; $k<$interval; $k++ ) { - if($c->aborted()) { untie(%cache); return $Str; } - $Str .= &DrawTable($k, $analyzeData->{'concepts'}, \%ConceptData); - $r->print($Str); - $Str = ''; - } - my $Answ=&Apache::lonnet::ssi($uri); - $Str .= '<br><b>Here you can see the Problem:</b><br>'.$Answ; - $Str .= '<form>'; - $r->print($Str); - - untie(%cache); - - return; + # Okay, they asked for data, so make sure we get the latest data. + &Apache::lonnet::logthis('got here for some reason'); +# &Apache::lonstatistics::Gather_Full_Student_Data($r); + $r->print(&OptionResponseProblemSelector()); } -#---- Problem Analysis Web Page ---------------------------------------------- - -sub IntervalOptions { - my ($selectedInterval)=@_; - - my $interval = 1; - for(my $n=1; $n<=7; $n++) { - if($selectedInterval == $n) { - $interval = $n; - } - } - - my $Ptr = '<select name="Interval">'."\n"; - for(my $n=1; $n<=7;$ n++) { - $Ptr .= '<option'; - if($interval == $n) { - $Ptr .= ' selected'; - } - $Ptr .= '>'.$n."</option>"."\n"; - } - $Ptr .= '</select>'."\n"; - - return $Ptr; +sub get_problem_symb { + my $problemstring = shift(); + my ($symb,$id) = ($problemstring=~ /^(.*):([^:]*)$/); + return ($symb,$id); } -sub OptionResponseTable { - my ($optionResponses,$cache)=@_; - - my @optionResponses=split(':::', $optionResponses); - my %partCount; - my %sequences; - my @orderedSequences=(); - foreach(@optionResponses) { - my ($sequence, $problemId, $part, undef)=split(':',$_); - $partCount{$problemId.':'.$part}++; - if(!defined($sequences{$sequence})) { - push(@orderedSequences, $sequence); - $sequences{$sequence} = $_; - } else { - $sequences{$sequence} .= ':::'.$_; - } - } - +sub CreateInterface { my $Str = ''; - - foreach my $sequence (@orderedSequences) { - my @optionProblems = split(':::', $sequences{$sequence}); - - $Str .= '<b>'.$cache->{$sequence.':title'}.'</b>'."\n"; - $Str .= "<table border=2><tr><th> \# </th><th> Problem Title </th>"; - $Str .= '<th> Resource </th><th> Analysis </th></tr>'."\n"; - - my $count = 1; - foreach(@optionProblems) { - my (undef, $problemId, $part, $response)= - split(':',$sequences{$sequence}); - my $uri = $cache->{$problemId.':source'}; - my $title = $cache->{$problemId.':title'}; - - my $Temp = '<a href="'.$uri.'" target="_blank">'.$title.'</a>'; - $Str .= '<tr>'; - $Str .= '<td> '.$count.' </td>'; - $Str .= '<td bgcolor="#DDFFDD">'.$Temp.'</td>'; - $Str .= '<td bgcolor="#EEFFCC">'.$uri.'</td>'; - if($partCount{$problemId.':'.$part} < 2) { - $Str .= '<td><input type="submit" name="Analyze:::'; - $Str .= $problemId.':'.$part.'" value="'; - $Str .= 'Part '.$part; - $Str .= '" /></td></tr>'."\n"; - } else { - my $value = $problemId.':'.$part.':'.$response; - $Str .= '<td><input type="submit" name="Analyze:::'.$value; - $Str .= '" value="'; - $Str .= 'Part '.$part.' Response '.$response; - $Str .= '" /></td></tr>'."\n"; - } - $count++; - } - $Str .= '</table><br>'."\n"; - } - - return $Str; -} - -#---- END Problem Analysis Web Page ------------------------------------------ - -#---- Analyze Web Page ------------------------------------------------------- - -#restore the student submissions and finding the result -sub OpStatus { - my ($problemID, $student, $ConceptData, $foil_to_concept, - $analyzeData, $cache)=@_; - - my $ids = $analyzeData->{'parts'}; - my @True = (); - my @False = (); - my $flag=0; - - my $tries=0; - - foreach my $id (@$ids) { - my ($part, $response) = split(/\./, $id); - my $time=$cache->{$student.':'.$problemID.':'.$part.':timestamp'}; - my @submissions = split(':::', $cache->{$student.':'.$problemID.':'. - $part.':'.$response. - ':submission'}); - foreach my $Resp (@submissions) { - my %submission=&Apache::lonnet::str2hash($Resp); - foreach (keys(%submission)) { - if($submission{$_}) { - my $answer = $analyzeData->{$id.'.foil.value.'.$_}; - if($submission{$_} eq $answer) { - &Decide("true", $foil_to_concept->{$_}, - $time, $ConceptData); - } else { - &Decide("false", $foil_to_concept->{$_}, - $time, $ConceptData); + $Str .= '<table cellspacing="5">'."\n"; + $Str .= '<tr>'; + $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>'; + $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>'; + $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>'; + $Str .= '</tr>'."\n"; + # + $Str .= '<tr><td align="center">'."\n"; + $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); + $Str .= '</td><td align="center">'; + $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); + $Str .= '</td><td align="center">'; + my $only_seq_with_assessments = sub { + my $s=shift; + if ($s->{'num_assess'} < 1) { + return 0; + } else { + return 1; + } + }; + $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5, + $only_seq_with_assessments); + $Str .= '</td></tr>'."\n"; + $Str .= '</table>'."\n"; + $Str .= '<input type="submit" name="ProblemAnalysis" value="'. + &mt('Analyze Problem').'" />'; + $Str .= ' 'x5; + $Str .= '<input type="submit" name="ClearCache" value="'. + &mt('Clear Caches').'" />'; + $Str .= ' 'x5; + return ($Str); +} + +sub OptionResponseProblemSelector { + my $Str; + $Str = "\n<table>\n"; + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + next if ($seq->{'num_assess'}<1); + my $seq_str = ''; + foreach my $res (@{$seq->{'contents'}}) { +# &Apache::lonnet::logthis('checking '.$res->{'title'}); +# next if ($res->{'type'} ne 'assessment'); + foreach my $part (@{$res->{'parts'}}) { + my $partdata = $res->{'partdata'}->{$part}; + if (! exists($partdata->{'option'}) || + $partdata->{'option'} == 0) { + next; + } + for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){ + my $respid = $partdata->{'ResponseIds'}->[$i]; + my $resptype = $partdata->{'ResponseTypes'}->[$i]; + if ($resptype eq 'option') { + my $value = &Apache::lonnet::escape($res->{'symb'}.':'.$respid); + my $checked = ''; + if ($ENV{'form.problemchoice'} eq $value) { + $checked = 'checked '; + } + $seq_str .= '<tr><td>'. + '<input type="radio" name="problemchoice" value="'.$value.'" '.$checked.'/>'. + '</td><td>'. + '<a href="'.$res->{'src'}.'">'.$res->{'title'}.'</a> '; + if ($partdata->{'option'} > 1) { + $seq_str .= &mt('response').' '.$respid; + } + $seq_str .= "</td></tr>\n"; } } } } - } - - return; -} - -sub DrawGraph { - my ($k,$Src,$Concepts,$ConceptData)=@_; - my $Max=0; - my @data1; - my @data2; - - # Adjust Data and find the Max - for (my $n=0; $n<(scalar @$Concepts); $n++ ) { - my $tmp=$Concepts->[$n]; - $data1[$n]=$ConceptData->{$tmp.'.'.$k.'.true'}; - $data2[$n]=$ConceptData->{$tmp.'.'.$k.'.false'}; - my $Sum=$data1[$n]+$data2[$n]; - if($Max < $Sum) { - $Max=$Sum; + if ($seq_str ne '') { + $Str .= '<tr><td> </td><td><b>'.$seq->{'title'}.'</b></td>'. + "</tr>\n".$seq_str; } } - for (my $n=0; $n<(scalar @$Concepts); $n++ ) { - if ($data1[$n]+$data2[$n]<$Max) { - $data2[$n]+=$Max-($data1[$n]+$data2[$n]); - } - } - my $P_No = (scalar @data1); - - if($Max > 1) { - $Max += (10 - $Max % 10); - $Max = int($Max); - } else { - $Max = 1; - } - - my $Titr=($ConceptData->{'Interval'}>1) ? $Src.'_interval_'.($k+1) : $Src; -# $GData=$Titr.'&Concepts'.'&'.'Answers'.'&'.$Max.'&'.$P_No.'&'.$data1.'&'.$data2; - my $GData = ''; - $GData = $Titr.'&Concepts&Answers&'.$Max.'&'.$P_No.'&'; - $GData .= (join(',',@data1)).'&'.(join(',',@data2)); - - return '<IMG src="/cgi-bin/graph.gif?'.$GData.'" border=1/>'; + $Str .= "</table>\n"; + return $Str; } -sub DrawTable { - my ($k,$Concepts,$ConceptData)=@_; - my $Max=0; - my @data1; - my @data2; - my $Correct=0; - my $Wrong=0; - for(my $n=0; $n<(scalar @$Concepts); $n++ ) { - my $tmp=$Concepts->[$n]; - $data1[$n]=$ConceptData->{$tmp.'.'.$k.'.true'}; - $Correct+=$data1[$n]; - $data2[$n]=$ConceptData->{$tmp.'.'.$k.'.false'}; - $Wrong+=$data2[$n]; - my $Sum=$data1[$n]+$data2[$n]; - if($Max < $Sum) { - $Max=$Sum; +sub get_resource_from_symb { + my ($symb) = @_; + &Apache::lonnet::logthis('target symb = :'.$symb.':'); + foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) { + foreach my $res (@{$seq->{'contents'}}) { + &Apache::lonnet::logthis('symb = :'.$res->{'symb'}.':'); + if ($res->{'symb'} eq $symb) { + return $res; + } } } - for(my $n=0; $n<(scalar @$Concepts); $n++ ) { - if ($data1[$n]+$data2[$n]<$Max) { - $data2[$n]+=$Max-($data1[$n]+$data2[$n]); - } - } - my $P_No = (scalar @data1); - my $Str = ''; -# $Str .= '<br><b>From: ['.localtime($ConceptData->{'Int.'.($k-1)}); -# $Str .= '] To: ['.localtime($ConceptData->{"Int.$k"}).']</b>'; - $Str .= "\n".'<table border=2>'. - "\n".'<tr>'. - "\n".'<th> # </th>'. - "\n".'<th> Concept </th>'. - "\n".'<th> Correct </th>'. - "\n".'<th> Wrong </th>'. - "\n".'</tr>'; - - for(my $n=0; $n<(scalar @$Concepts); $n++ ) { - $Str .= '<tr>'."\n"; - $Str .= '<td>'.($n+1).'</td>'."\n"; - my ($currentConcept) = split('::',$Concepts->[$n]); - $Str .= '<td bgcolor="EEFFCC">'.$currentConcept; - $Str .= '</td>'."\n"; - $Str .= '<td bgcolor="DDFFDD">'.$data1[$n].'</td>'."\n"; - $Str .= '<td bgcolor="FFDDDD">'.$data2[$n].'</td>'."\n"; - $Str .= '</tr>'."\n"; - } - $Str .= '<td></td><td><b>From:['.localtime($ConceptData->{'Int.'.$k}); - $Str .= '] To: ['.localtime($ConceptData->{'Int.'.($k+1)}-1); - $Str .= ']</b></td><td>'.$Correct.'</td><td>'.$Wrong.'</td>'; - $Str .= '</table>'."\n"; - - return $Str; -#$Apache::lonxml::debug=1; -#&Apache::lonhomework::showhash(%ConceptData); -#$Apache::lonxml::debug=0; + return undef; } -#---- END Analyze Web Page ---------------------------------------------- +=pod -sub Decide { - #deciding the true or false answer belongs to each interval - my ($type,$concept,$time,$ConceptData)=@_; - my $k=0; - while($time > $ConceptData->{'Int.'.($k+1)} && - $k < $ConceptData->{'Interval'}) { - $k++; - } - $ConceptData->{$concept.'.'.$k.'.'.$type}++; +sub InitAnalysis { + my ($resource,$sname,$sdom)=@_; + my $symb = $resource-> + my $URI = $hash{'src_'.$rid}; - return; -} + my $Answ=&Apache::lonnet::ssi($URI,('grade_target' => 'analyze', + 'grade_username' => $sname, + 'grade_domain' => $sdom, + 'grade_courseid' => $cid, + 'grade_symb' => $symb)); +# my $Answ=&Apache::lonnet::ssi($URI,('grade_target' => 'analyze')); -sub InitAnalysis { - my ($uri,$part,$responseId,$problem,$student,$courseID)=@_; - my ($name,$domain)=split(/\:/,$student); + (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2); + %Answer=(); + %Answer=&Apache::lonnet::str2hash($Answ); - my %analyzeData; - # Render the student's view of the problem. $Answ is the problem - # Stringafied - my $Answ=&Apache::lonnet::ssi($uri,('grade_target' => 'analyze', - 'grade_username' => $name, - 'grade_domain' => $domain, - 'grade_courseid' => $courseID, - 'grade_symb' => $problem)); - my ($Answer)=&Apache::lonnet::str2hashref($Answ); - - my $found = 0; - my @parts=(); - if(defined($responseId)) { - foreach (@{$Answer->{'parts'}}) { - if($_ eq $part.'.'.$responseId) { - push(@parts, $_); - $found = 1; - last; - } - } - } else { - foreach (@{$Answer->{'parts'}}) { - if($_ =~ /$part/) { - push(@parts, $_); - $found = 1; - last; - } - } + my $parts=''; + foreach my $elm (@{$Answer{"parts"}}) { + $parts.="$elm,"; } - - if($found == 0) { - $analyzeData{'error'} = 'No parts matching selected values'; - return \%analyzeData; + chop($parts); + my $conc=''; + foreach my $elm (@{$Answer{"$parts.concepts"}}) { + $conc.="$elm@"; } + chop($conc); - my @Concepts=(); - my %foil_to_concept; - foreach my $currentPart (@parts) { - if(defined($Answer->{$currentPart.'.concepts'})) { - foreach my $concept (@{$Answer->{$currentPart.'.concepts'}}) { - push(@Concepts, $concept); - foreach my $foil (@{$Answer->{$currentPart.'.concept.'. - $concept}}) { - $analyzeData{$currentPart.'.foil.value.'.$foil} = - $Answer->{$currentPart.'.foil.value.'.$foil}; - $foil_to_concept{$foil} = $concept; - } - } - } else { - foreach (keys(%$Answer)) { - if(/$currentPart.foil\.value\.(.*)$/) { - push(@Concepts, $1); - $foil_to_concept{$1} = $1; - $analyzeData{$currentPart.'.foil.value.'.$1} = - $Answer->{$currentPart.'.foil.value.'.$1}; - } - } + @Concepts=split(/\@/,$conc); + foreach my $concept (@{$Answer{"$parts.concepts"}}) { + foreach my $foil (@{$Answer{"$parts.concept.$concept"}}) { + $foil_to_concept{$foil} = $concept; + #$ConceptData{$foil} = $Answer{"$parts.foil.value.$foil"}; } } - - $analyzeData{'parts'} = \@parts; - $analyzeData{'concepts'} = \@Concepts; - $analyzeData{'foil_to_concept'} = \%foil_to_concept; - - return \%analyzeData; + return $symb; } -sub Interval { - my ($part,$symb,$interval,$Concepts,$ConceptData)=@_; - my $Int=$interval; - my $due = &Apache::lonnet::EXT('resource.'.$part.'.duedate',$symb); - my $opn = &Apache::lonnet::EXT('resource.'.$part.'.opendate',$symb); - my $add=int(($due-$opn)/$Int); - $ConceptData->{'Int.0'}=$opn; - for(my $i=1; $i<$Int; $i++) { - $ConceptData->{'Int.'.$i}=$opn+$i*$add; - } - $ConceptData->{'Int.'.$Int}=$due; - for(my $i=0; $i<$Int; $i++) { - for(my $n=0; $n<(scalar @$Concepts); $n++ ) { - my $tmp=$Concepts->[$n]; - $ConceptData->{$tmp.'.'.$i.'.true'}=0; - $ConceptData->{$tmp.'.'.$i.'.false'}=0; - } - } -} +=cut + 1; + __END__