|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00028 defined('MOODLE_INTERNAL') || die(); 00029 00030 require_once(dirname(dirname(__FILE__)) . '/lib.php'); // interface definition 00031 require_once($CFG->libdir . '/gradelib.php'); 00032 00036 class workshop_best_evaluation implements workshop_evaluation { 00037 00039 protected $workshop; 00040 00042 protected $settings; 00043 00050 public function __construct(workshop $workshop) { 00051 global $DB; 00052 $this->workshop = $workshop; 00053 $this->settings = $DB->get_record('workshopeval_best_settings', array('workshopid' => $this->workshop->id)); 00054 } 00055 00067 public function update_grading_grades(stdclass $settings, $restrict=null) { 00068 global $DB; 00069 00070 // remember the recently used settings for this workshop 00071 if (empty($this->settings)) { 00072 $record = new stdclass(); 00073 $record->workshopid = $this->workshop->id; 00074 $record->comparison = $settings->comparison; 00075 $DB->insert_record('workshopeval_best_settings', $record); 00076 } elseif ($this->settings->comparison != $settings->comparison) { 00077 $DB->set_field('workshopeval_best_settings', 'comparison', $settings->comparison, 00078 array('workshopid' => $this->workshop->id)); 00079 } 00080 00081 // get the grading strategy instance 00082 $grader = $this->workshop->grading_strategy_instance(); 00083 00084 // get the information about the assessment dimensions 00085 $diminfo = $grader->get_dimensions_info(); 00086 00087 // fetch a recordset with all assessments to process 00088 $rs = $grader->get_assessments_recordset($restrict); 00089 $batch = array(); // will contain a set of all assessments of a single submission 00090 $previous = null; // a previous record in the recordset 00091 foreach ($rs as $current) { 00092 if (is_null($previous)) { 00093 // we are processing the very first record in the recordset 00094 $previous = $current; 00095 } 00096 if ($current->submissionid == $previous->submissionid) { 00097 $batch[] = $current; 00098 } else { 00099 // process all the assessments of a single submission 00100 $this->process_assessments($batch, $diminfo, $settings); 00101 // start with a new batch to be processed 00102 $batch = array($current); 00103 $previous = $current; 00104 } 00105 } 00106 // do not forget to process the last batch! 00107 $this->process_assessments($batch, $diminfo, $settings); 00108 $rs->close(); 00109 } 00110 00116 public function get_settings_form(moodle_url $actionurl=null) { 00117 global $CFG; // needed because the included files use it 00118 global $DB; 00119 require_once(dirname(__FILE__) . '/settings_form.php'); 00120 00121 $customdata['workshop'] = $this->workshop; 00122 $customdata['current'] = $this->settings; 00123 $attributes = array('class' => 'evalsettingsform best'); 00124 00125 return new workshop_best_evaluation_settings_form($actionurl, $customdata, 'post', '', $attributes); 00126 } 00127 00135 public static function delete_instance($workshopid) { 00136 global $DB; 00137 00138 $DB->delete_records('workshopeval_best_settings', array('workshopid' => $workshopid)); 00139 } 00140 00142 // Internal methods // 00144 00153 protected function process_assessments(array $assessments, array $diminfo, stdclass $settings) { 00154 global $DB; 00155 00156 if (empty($assessments)) { 00157 return; 00158 } 00159 00160 // reindex the passed flat structure to be indexed by assessmentid 00161 $assessments = $this->prepare_data_from_recordset($assessments); 00162 00163 // normalize the dimension grades to the interval 0 - 100 00164 $assessments = $this->normalize_grades($assessments, $diminfo); 00165 00166 // get a hypothetical average assessment 00167 $average = $this->average_assessment($assessments); 00168 00169 // calculate variance of dimension grades 00170 $variances = $this->weighted_variance($assessments); 00171 foreach ($variances as $dimid => $variance) { 00172 $diminfo[$dimid]->variance = $variance; 00173 } 00174 00175 // for every assessment, calculate its distance from the average one 00176 $distances = array(); 00177 foreach ($assessments as $asid => $assessment) { 00178 $distances[$asid] = $this->assessments_distance($assessment, $average, $diminfo, $settings); 00179 } 00180 00181 // identify the best assessments - that is those with the shortest distance from the best assessment 00182 $bestids = array_keys($distances, min($distances)); 00183 00184 // for every assessment, calculate its distance from the nearest best assessment 00185 $distances = array(); 00186 foreach ($bestids as $bestid) { 00187 $best = $assessments[$bestid]; 00188 foreach ($assessments as $asid => $assessment) { 00189 $d = $this->assessments_distance($assessment, $best, $diminfo, $settings); 00190 if (!is_null($d) and (!isset($distances[$asid]) or $d < $distances[$asid])) { 00191 $distances[$asid] = $d; 00192 } 00193 } 00194 } 00195 00196 // calculate the grading grade 00197 foreach ($distances as $asid => $distance) { 00198 $gradinggrade = (100 - $distance); 00199 if ($gradinggrade < 0) { 00200 $gradinggrade = 0; 00201 } 00202 if ($gradinggrade > 100) { 00203 $gradinggrade = 100; 00204 } 00205 $grades[$asid] = grade_floatval($gradinggrade); 00206 } 00207 00208 // if the new grading grade differs from the one stored in database, update it 00209 // we do not use set_field() here because we want to pass $bulk param 00210 foreach ($grades as $assessmentid => $grade) { 00211 if (grade_floats_different($grade, $assessments[$assessmentid]->gradinggrade)) { 00212 // the value has changed 00213 $record = new stdclass(); 00214 $record->id = $assessmentid; 00215 $record->gradinggrade = grade_floatval($grade); 00216 // do not set timemodified here, it contains the timestamp of when the form was 00217 // saved by the peer reviewer, not when it was aggregated 00218 $DB->update_record('workshop_assessments', $record, true); // bulk operations expected 00219 } 00220 } 00221 00222 // done. easy, heh? ;-) 00223 } 00224 00231 protected function prepare_data_from_recordset($assessments) { 00232 $data = array(); // to be returned 00233 foreach ($assessments as $a) { 00234 $id = $a->assessmentid; // just an abbreviation 00235 if (!isset($data[$id])) { 00236 $data[$id] = new stdclass(); 00237 $data[$id]->assessmentid = $a->assessmentid; 00238 $data[$id]->weight = $a->assessmentweight; 00239 $data[$id]->reviewerid = $a->reviewerid; 00240 $data[$id]->gradinggrade = $a->gradinggrade; 00241 $data[$id]->submissionid = $a->submissionid; 00242 $data[$id]->dimgrades = array(); 00243 } 00244 $data[$id]->dimgrades[$a->dimensionid] = $a->grade; 00245 } 00246 return $data; 00247 } 00248 00259 protected function normalize_grades(array $assessments, array $diminfo) { 00260 foreach ($assessments as $asid => $assessment) { 00261 foreach ($assessment->dimgrades as $dimid => $dimgrade) { 00262 $dimmin = $diminfo[$dimid]->min; 00263 $dimmax = $diminfo[$dimid]->max; 00264 if ($dimmin == $dimmax) { 00265 $assessment->dimgrades[$dimid] = grade_floatval($dimmax); 00266 } else { 00267 $assessment->dimgrades[$dimid] = grade_floatval(($dimgrade - $dimmin) / ($dimmax - $dimmin) * 100); 00268 } 00269 } 00270 } 00271 return $assessments; 00272 } 00273 00282 protected function average_assessment(array $assessments) { 00283 $sumdimgrades = array(); 00284 foreach ($assessments as $a) { 00285 foreach ($a->dimgrades as $dimid => $dimgrade) { 00286 if (!isset($sumdimgrades[$dimid])) { 00287 $sumdimgrades[$dimid] = 0; 00288 } 00289 $sumdimgrades[$dimid] += $dimgrade * $a->weight; 00290 } 00291 } 00292 00293 $sumweights = 0; 00294 foreach ($assessments as $a) { 00295 $sumweights += $a->weight; 00296 } 00297 if ($sumweights == 0) { 00298 // unable to calculate average assessment 00299 return null; 00300 } 00301 00302 $average = new stdclass(); 00303 $average->dimgrades = array(); 00304 foreach ($sumdimgrades as $dimid => $sumdimgrade) { 00305 $average->dimgrades[$dimid] = grade_floatval($sumdimgrade / $sumweights); 00306 } 00307 return $average; 00308 } 00309 00322 protected function weighted_variance(array $assessments) { 00323 $first = reset($assessments); 00324 if (empty($first)) { 00325 return null; 00326 } 00327 $dimids = array_keys($first->dimgrades); 00328 $asids = array_keys($assessments); 00329 $vars = array(); // to be returned 00330 foreach ($dimids as $dimid) { 00331 $n = 0; 00332 $s = 0; 00333 $sumweight = 0; 00334 foreach ($asids as $asid) { 00335 $x = $assessments[$asid]->dimgrades[$dimid]; // value (data point) 00336 $weight = $assessments[$asid]->weight; // the values' weight 00337 if ($weight == 0) { 00338 continue; 00339 } 00340 if ($n == 0) { 00341 $n = 1; 00342 $mean = $x; 00343 $s = 0; 00344 $sumweight = $weight; 00345 } else { 00346 $n++; 00347 $temp = $weight + $sumweight; 00348 $q = $x - $mean; 00349 $r = $q * $weight / $temp; 00350 $s = $s + $sumweight * $q * $r; 00351 $mean = $mean + $r; 00352 $sumweight = $temp; 00353 } 00354 } 00355 if ($sumweight > 0 and $n > 1) { 00356 // for the sample: $vars[$dimid] = ($s * $n) / (($n - 1) * $sumweight); 00357 // for the population: 00358 $vars[$dimid] = $s / $sumweight; 00359 } else { 00360 $vars[$dimid] = null; 00361 } 00362 } 00363 return $vars; 00364 } 00365 00381 protected function assessments_distance(stdclass $assessment, stdclass $referential, array $diminfo, stdclass $settings) { 00382 $distance = 0; 00383 $n = 0; 00384 foreach (array_keys($assessment->dimgrades) as $dimid) { 00385 $agrade = $assessment->dimgrades[$dimid]; 00386 $rgrade = $referential->dimgrades[$dimid]; 00387 $var = $diminfo[$dimid]->variance; 00388 $weight = $diminfo[$dimid]->weight; 00389 $n += $weight; 00390 00391 // variations very close to zero are too sensitive to a small change of data values 00392 if ($var > 0.01 and $agrade != $rgrade) { 00393 $absdelta = abs($agrade - $rgrade); 00394 $reldelta = pow($agrade - $rgrade, 2) / ($settings->comparison * $var); 00395 $distance += $absdelta * $reldelta * $weight; 00396 } 00397 } 00398 if ($n > 0) { 00399 // average distance across all dimensions 00400 return round($distance / $n, 4); 00401 } else { 00402 return null; 00403 } 00404 } 00405 }