--- loncom/interface/loncoursedata.pm 2003/03/28 15:19:35 1.64 +++ loncom/interface/loncoursedata.pm 2003/09/24 18:01:01 1.88 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: loncoursedata.pm,v 1.64 2003/03/28 15:19:35 matthew Exp $ +# $Id: loncoursedata.pm,v 1.88 2003/09/24 18:01:01 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -104,11 +104,14 @@ sub get_sequence_assessment_data { my $fn=$ENV{'request.course.fn'}; ## ## use navmaps - my $navmap = Apache::lonnavmaps::navmap->new(Apache->request,$fn.".db", - $fn."_parms.db",1,0); + my $navmap = Apache::lonnavmaps::navmap->new(); if (!defined($navmap)) { return 'Can not open Coursemap'; } + # We explicity grab the top level map because I am not sure we + # are pulling it from the iterator. + my $top_level_map = $navmap->getById('0.0'); + # my $iterator = $navmap->getIterator(undef, undef, undef, 1); my $curRes = $iterator->next(); # Top level sequence ## @@ -118,8 +121,9 @@ sub get_sequence_assessment_data { ## resources. This means we have to start out with something to look ## at. my $title = $ENV{'course.'.$ENV{'request.course.id'}.'.description'}; - my $symb = 'top'; - my $src = 'not applicable'; + my $symb = $top_level_map->symb(); + my $src = $top_level_map->src(); + my $randompick = $top_level_map->randompick(); # my @Sequences; my @Assessments; @@ -130,29 +134,44 @@ sub get_sequence_assessment_data { type => 'container', num_assess => 0, num_assess_parts => 0, - contents => [], }; + contents => [], + randompick => $randompick, + }; push (@Sequences,$top); push (@Nested_Sequences, $top); # # We need to keep track of which sequences contain homework problems # + my $previous_too; my $previous; - $curRes = $iterator->next(); # BEGIN_MAP - $curRes = $iterator->next(); # The first item in the top level map. while (scalar(@Nested_Sequences)) { + $previous_too = $previous; $previous = $curRes; $curRes = $iterator->next(); my $currentmap = $Nested_Sequences[-1]; # Last one on the stack if ($curRes == $iterator->BEGIN_MAP()) { + if (! ref($previous)) { + $previous = $previous_too; + } + if (! ref($previous)) { + next; + } # get the map itself, instead of BEGIN_MAP $title = $previous->title(); + $title =~ s/\:/\&\#058;/g; $symb = $previous->symb(); $src = $previous->src(); + # pick up the filename if there is no title available + if (! defined($title) || $title eq '') { + ($title) = ($src=~/\/([^\/]*)$/); + } + $randompick = $previous->randompick(); my $newmap = { title => $title, src => $src, symb => $symb, type => 'container', num_assess => 0, + randompick => $randompick, contents => [], }; push (@{$currentmap->{'contents'}},$newmap); # this is permanent @@ -168,15 +187,24 @@ sub get_sequence_assessment_data { next if (! $curRes->is_problem());# && !$curRes->randomout); # Okay, from here on out we only deal with assessments $title = $curRes->title(); + $title =~ s/\:/\&\#058;/g; $symb = $curRes->symb(); $src = $curRes->src(); my $parts = $curRes->parts(); + my %partdata; + foreach my $part (@$parts) { + my @Responses = $curRes->responseType($part); + my @Ids = $curRes->responseIds($part); + $partdata{$part}->{'ResponseTypes'}= \@Responses; + $partdata{$part}->{'ResponseIds'} = \@Ids; + } my $assessment = { title => $title, src => $src, symb => $symb, type => 'assessment', parts => $parts, num_parts => scalar(@$parts), + partdata => \%partdata, }; push(@Assessments,$assessment); push(@{$currentmap->{'contents'}},$assessment); @@ -211,6 +239,39 @@ sub LoadDiscussion { return \%Discuss; } +################################################ +################################################ + +=pod + +=item &GetUserName(username,userdomain) + +Returns a hash with the following entries: + 'firstname', 'middlename', 'lastname', 'generation', and 'fullname' + + 'fullname' is the result of &Apache::loncoursedata::ProcessFullName. + +=cut + +################################################ +################################################ +sub GetUserName { + my ($username,$userdomain) = @_; + $username = $ENV{'user.name'} if (! defined($username)); + $userdomain = $ENV{'user.domain'} if (! defined($username)); + my %userenv = &Apache::lonnet::get('environment', + ['firstname','middlename','lastname','generation'], + $userdomain,$username); + $userenv{'fullname'} = &ProcessFullName($userenv{'lastname'}, + $userenv{'generation'}, + $userenv{'firstname'}, + $userenv{'middlename'}); + return %userenv; +} + +################################################ +################################################ + =pod =item &ProcessFullName() @@ -221,6 +282,8 @@ is Lastname generation, firstname middle =cut +################################################ +################################################ sub ProcessFullName { my ($lastname, $generation, $firstname, $middlename)=@_; my $Str = ''; @@ -333,12 +396,12 @@ internally to the MySQL database and is (stored in the students environment). This table has its PRIMARY KEY on the 'student' (100 characters). -=item $updatetime_table +=item $studentdata_table -The updatetime_table has two columns. The first is 'student' (100 characters, -typically username:domain). The second is 'updatetime', which is an unsigned -integer, NOT a MySQL date. This table has its PRIMARY KEY on 'student' (100 -characters). +The studentdata_table has four columns. The first is 'student_id', the unique +id of the student. The second is the time the students data was last updated. +The third is the students section. The fourth is the students current +classification. This table has its PRIMARY KEY on 'student_id'. =item $performance_table @@ -396,7 +459,7 @@ my $current_course =''; my $symb_table; my $part_table; my $student_table; -my $updatetime_table; +my $studentdata_table; my $performance_table; my $parameters_table; @@ -422,6 +485,13 @@ sub init_dbs { my $courseid = shift; &setup_table_names($courseid); # + # Drop any of the existing tables + foreach my $table ($symb_table,$part_table,$student_table, + $studentdata_table,$performance_table, + $parameters_table) { + &Apache::lonmysql::drop_table($table); + } + # # Note - changes to this table must be reflected in the code that # stores the data (calls &Apache::lonmysql::store_row with this table # id @@ -464,22 +534,28 @@ sub init_dbs { { name => 'student', type => 'VARCHAR(100)', restrictions => 'NOT NULL'}, + { name => 'classification', + type => 'varchar(100)', }, ], 'PRIMARY KEY' => ['student (100)'], 'KEY' => [{ columns => ['student_id']},], }; # - my $updatetime_table_def = { - id => $updatetime_table, + my $studentdata_table_def = { + id => $studentdata_table, permanent => 'no', - columns => [{ name => 'student', - type => 'VARCHAR(100)', + columns => [{ name => 'student_id', + type => 'MEDIUMINT UNSIGNED', restrictions => 'NOT NULL UNIQUE',}, { name => 'updatetime', type => 'INT UNSIGNED', restrictions => 'NOT NULL' }, + { name => 'section', + type => 'VARCHAR(100)'}, + { name => 'classification', + type => 'VARCHAR(100)', }, ], - 'PRIMARY KEY' => ['student (100)'], + 'PRIMARY KEY' => ['student_id'], }; # my $performance_table_def = { @@ -494,6 +570,9 @@ sub init_dbs { { name => 'part_id', type => 'MEDIUMINT UNSIGNED', restrictions => 'NOT NULL' }, + { name => 'part', + type => 'VARCHAR(100)', + restrictions => 'NOT NULL'}, { name => 'solved', type => 'TINYTEXT' }, { name => 'tries', @@ -553,9 +632,9 @@ sub init_dbs { return 3; } # - $tableid = &Apache::lonmysql::create_table($updatetime_table_def); + $tableid = &Apache::lonmysql::create_table($studentdata_table_def); if (! defined($tableid)) { - &Apache::lonnet::logthis("error creating updatetime_table: ". + &Apache::lonnet::logthis("error creating studentdata_table: ". &Apache::lonmysql::get_error()); return 4; } @@ -581,6 +660,36 @@ sub init_dbs { =pod +=item &delete_caches() + +=cut + +################################################ +################################################ +sub delete_caches { + my $courseid = shift; + $courseid = $ENV{'request.course.id'} if (! defined($courseid)); + # + &setup_table_names($courseid); + # + my $dbh = &Apache::lonmysql::get_dbh(); + foreach my $table ($symb_table,$part_table,$student_table, + $studentdata_table,$performance_table, + $parameters_table ){ + my $command = 'DROP TABLE '.$table.';'; + $dbh->do($command); + if ($dbh->err) { + &Apache::lonnet::logthis($command.' resulted in error: '.$dbh->errstr); + } + } + return; +} + +################################################ +################################################ + +=pod + =item &get_part_id() Get the MySQL id of a problem part string. @@ -748,7 +857,7 @@ sub get_student_id { $have_read_student_table = 1; } if (! exists($ids_by_student{$student})) { - &Apache::lonmysql::store_row($student_table,[undef,$student]); + &Apache::lonmysql::store_row($student_table,[undef,$student,undef]); undef(%ids_by_student); my @Result = &Apache::lonmysql::get_rows($student_table); foreach (@Result) { @@ -826,7 +935,7 @@ sub update_student_data { $sname.':'.$sdom.' in course '.$courseid. ':'.$tmp[0]); $returnstatus = 'error getting data'; - return $returnstatus; + return ($returnstatus,undef); } if (scalar(@tmp) < 1) { return ('no data',undef); @@ -850,7 +959,7 @@ sub update_student_data { my $num_parameters = 0; my $store_performance_command = 'INSERT INTO '.$performance_table. ' VALUES '."\n"; - return 'error' if (! defined($dbh)); + return ('error',undef) if (! defined($dbh)); while (my ($current_symb,$param_hash) = each(%student_data)) { # # make sure the symb is set up properly @@ -862,7 +971,8 @@ sub update_student_data { if ($parameter !~ /(timestamp|resource\.(.*)\.(solved|tries|awarded|award|awarddetail|previous))/) { $newstring = "('".join("','", $symb_id,$student_id, - $parameter,$value)."'),\n"; + $parameter)."',". + $dbh->quote($value)."),\n"; $num_parameters ++; if ($newstring !~ /''/) { $store_parameters_command .= $newstring; @@ -881,12 +991,12 @@ sub update_student_data { my $awarddetail = $param_hash->{'resource.'.$part.'.awarddetail'}; my $timestamp = $param_hash->{'timestamp'}; # - $solved = '' if (! defined($awarded)); + $solved = '' if (! defined($solved)); $tries = '' if (! defined($tries)); $awarded = '' if (! defined($awarded)); $award = '' if (! defined($award)); $awarddetail = '' if (! defined($awarddetail)); - $newstring = "('".join("','",$symb_id,$student_id,$part_id, + $newstring = "('".join("','",$symb_id,$student_id,$part_id,$part, $solved,$tries,$awarded,$award, $awarddetail,$timestamp)."'),\n"; $store_performance_command .= $newstring; @@ -902,21 +1012,23 @@ sub update_student_data { if ($dbh->err()) { &Apache::lonnet::logthis(' bigass insert error:'.$dbh->errstr()); &Apache::lonnet::logthis('command = '.$store_parameters_command); + &Apache::lonnet::logthis('rows_stored = '.$rows_stored); + &Apache::lonnet::logthis('student_id = '.$student_id); $returnstatus = 'error: unable to insert parameters into database'; - return $returnstatus,\%student_data; + return ($returnstatus,\%student_data); } $dbh->do($store_performance_command); if ($dbh->err()) { &Apache::lonnet::logthis(' bigass insert error:'.$dbh->errstr()); &Apache::lonnet::logthis('command = '.$store_performance_command); $returnstatus = 'error: unable to insert performance into database'; - return $returnstatus,\%student_data; + return ($returnstatus,\%student_data); } $elapsed += Time::HiRes::time - $start; # # Set the students update time - &Apache::lonmysql::replace_row($updatetime_table, - [$student,$time_of_retrieval]); + &Apache::lonmysql::replace_row($studentdata_table, + [$student_id,$time_of_retrieval,undef,undef]); return ($returnstatus,\%student_data); } @@ -932,7 +1044,7 @@ Input: $sname, $sdom, $courseid Output: $status, $data This routine ensures the data for a given student is up to date. It calls -&init_dbs() if the tables do not exist. The $updatetime_table is queried +&init_dbs() if the tables do not exist. The $studentdata_table is queried to determine the time of the last update. If the students data is out of date, &update_student_data() is called. The return values from the call to &update_student_data() are returned. @@ -952,21 +1064,21 @@ sub ensure_current_data { # # if the tables do not exist, make them my @CurrentTable = &Apache::lonmysql::tables_in_db(); - my ($found_symb,$found_student,$found_part,$found_update, + my ($found_symb,$found_student,$found_part,$found_studentdata, $found_performance,$found_parameters); foreach (@CurrentTable) { $found_symb = 1 if ($_ eq $symb_table); $found_student = 1 if ($_ eq $student_table); $found_part = 1 if ($_ eq $part_table); - $found_update = 1 if ($_ eq $updatetime_table); + $found_studentdata = 1 if ($_ eq $studentdata_table); $found_performance = 1 if ($_ eq $performance_table); $found_parameters = 1 if ($_ eq $parameters_table); } - if (!$found_symb || !$found_update || + if (!$found_symb || !$found_studentdata || !$found_student || !$found_part || !$found_performance || !$found_parameters) { if (&init_dbs($courseid)) { - return 'error'; + return ('error',undef); } } # @@ -976,9 +1088,9 @@ sub ensure_current_data { ($sdom,$sname,$courseid.'.db', $Apache::lonnet::perlvar{'lonUsersDir'}); # - my $student = $sname.':'.$sdom; - my @Result = &Apache::lonmysql::get_rows($updatetime_table, - "student ='$student'"); + my $student_id = &get_student_id($sname,$sdom); + my @Result = &Apache::lonmysql::get_rows($studentdata_table, + "student_id ='$student_id'"); my $data = undef; if (@Result) { $updatetime = $Result[0]->[1]; @@ -1030,21 +1142,21 @@ sub get_student_data_from_performance_ca # my $dbh = &Apache::lonmysql::get_dbh(); my $request = "SELECT ". - "d.symb,c.part,a.solved,a.tries,a.awarded,a.award,a.awarddetail,". + "d.symb,a.part,a.solved,a.tries,a.awarded,a.award,a.awarddetail,". "a.timestamp "; if (defined($student)) { $request .= "FROM $student_table AS b ". "LEFT JOIN $performance_table AS a ON b.student_id=a.student_id ". - "LEFT JOIN $part_table AS c ON c.part_id = a.part_id ". +# "LEFT JOIN $part_table AS c ON c.part_id = a.part_id ". "LEFT JOIN $symb_table AS d ON d.symb_id = a.symb_id ". "WHERE student='$student'"; if (defined($symb) && $symb ne '') { - $request .= " AND d.symb='".$dbh->quote($symb)."'"; + $request .= " AND d.symb=".$dbh->quote($symb); } } elsif (defined($symb) && $symb ne '') { $request .= "FROM $symb_table as d ". "LEFT JOIN $performance_table AS a ON d.symb_id=a.symb_id ". - "LEFT JOIN $part_table AS c ON c.part_id = a.part_id ". +# "LEFT JOIN $part_table AS c ON c.part_id = a.part_id ". "LEFT JOIN $student_table AS b ON b.student_id = a.student_id ". "WHERE symb='".$dbh->quote($symb)."'"; } @@ -1070,6 +1182,9 @@ sub get_student_data_from_performance_ca $studentdata->{$symb}->{$base.'.awarddetail'} = $awarddetail; $studentdata->{$symb}->{'timestamp'} = $time if (defined($time) && $time ne ''); } + if (defined($symb) && $symb ne '') { + $studentdata = $studentdata->{$symb}; + } return $studentdata; } @@ -1119,10 +1234,24 @@ sub get_current_state { return () if (! defined($sname) || ! defined($sdom)); # my ($status,$data) = &ensure_current_data($sname,$sdom,$courseid); - # - if (defined($data)) { +# &Apache::lonnet::logthis +# ('sname = '.$sname. +# ' domain = '.$sdom. +# ' status = '.$status. +# ' data is '.(defined($data)?'defined':'undefined')); +# while (my ($symb,$hash) = each(%$data)) { +# &Apache::lonnet::logthis($symb."\n----------------------------------"); +# while (my ($key,$value) = each (%$hash)) { +# &Apache::lonnet::logthis(" ".$key." = ".$value); +# } +# } + # + if (defined($data) && defined($symb) && ref($data->{$symb})) { + return %{$data->{$symb}}; + } elsif (defined($data) && ! defined($symb) && ref($data)) { return %$data; - } elsif ($status eq 'no data') { + } + if ($status eq 'no data') { return (); } else { if ($status ne 'okay' && $status ne '') { @@ -1163,27 +1292,31 @@ Each hash must contain at least the 'use =back -Outputs: See the code for up to date information. +Outputs: See the code for up to date information. A hash reference is +returned. The hash has the following keys defined: =over 4 -=item $num The number of students attempting the problem - -=item $tries The total number of tries for the students - -=item $mod The maximum number of tries taken - -=item $mean The average number of tries +=item num_students The number of students attempting the problem + +=item tries The total number of tries for the students + +=item max_tries The maximum number of tries taken + +=item mean_tries The average number of tries + +=item num_solved The number of students able to solve the problem + +=item num_override The number of students whose answer is 'correct_by_override' + +=item deg_of_diff The degree of difficulty of the problem + +=item std_tries The standard deviation of the number of tries + +=item skew_tries The skew of the number of tries -=item $Solved The number of students able to solve the problem - -=item $solved The number of students whose answer is 'correct_by_override' - -=item $DegOfDiff The degree of difficulty of the problem - -=item $STD The standard deviation of the number of tries - -=item $SKEW The skew of the number of tries +=item per_wrong The number of students attempting the problem who were not +able to answer it correctly. =back @@ -1247,17 +1380,47 @@ sub get_problem_statistics { $solved = 0 if (! defined($solved)); # my $DegOfDiff = 'nan'; - $DegOfDiff = 1-($Solved + $solved)/$tries if ($tries>0); + $DegOfDiff = 1-($Solved)/$tries if ($tries>0); my $SKEW = 'nan'; + my $wrongpercent = 0; if ($num > 0) { ($SKEW) = &execute_SQL_request($dbh,'SELECT SQRT(SUM('. 'POWER(tries - '.$STD.',3)'. '))/'.$num.' FROM '.$stats_table); + $wrongpercent=int(10*100*($num-$Solved+$solved)/$num)/10; } # $dbh->do('DROP TABLE '.$stats_table); # May return an error - return ($num,$tries,$mod,$mean,$Solved,$solved,$DegOfDiff,$STD,$SKEW); + # + # Store in metadata + # + if ($num) { + my %storestats=(); + + my $urlres=(&Apache::lonnet::decode_symb($symb))[2]; + + $storestats{$courseid.'___'.$urlres.'___timestamp'}=time; + $storestats{$courseid.'___'.$urlres.'___stdno'}=$num; + $storestats{$courseid.'___'.$urlres.'___avetries'}=$mean; + $storestats{$courseid.'___'.$urlres.'___difficulty'}=$DegOfDiff; + + $urlres=~/^(\w+)\/(\w+)/; + &Apache::lonnet::put('nohist_resevaldata',\%storestats,$1,$2); + } + # + # Return result + # + return { num_students => $num, + tries => $tries, + max_tries => $mod, + mean_tries => $mean, + std_tries => $STD, + skew_tries => $SKEW, + num_solved => $Solved, + num_override => $solved, + per_wrong => $wrongpercent, + deg_of_diff => $DegOfDiff }; } sub execute_SQL_request { @@ -1316,7 +1479,7 @@ sub setup_table_names { $symb_table = $base_id.'_'.'symb'; $part_table = $base_id.'_'.'part'; $student_table = $base_id.'_'.'student'; - $updatetime_table = $base_id.'_'.'updatetime'; + $studentdata_table = $base_id.'_'.'studentdata'; $performance_table = $base_id.'_'.'performance'; $parameters_table = $base_id.'_'.'parameters'; return;