ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç lib.php000064400000006327152267043470006045 0ustar00. /** * This file defines interface of all grading evaluation classes * * @package mod_workshop * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/lib/formslib.php'); /** * Base class for all grading evaluation subplugins. */ abstract class workshop_evaluation { /** @var workshop the parent workshop instance */ protected $workshop; /** * Calculates grades for assessment and updates 'gradinggrade' fields in 'workshop_assessments' table * * @param stdClass $settings settings for this round of evaluation * @param null|int|array $restrict if null, update all reviewers, otherwise update just grades for the given reviewers(s) */ abstract public function update_grading_grades(stdClass $settings, $restrict=null); /** * Returns an instance of the form to provide evaluation settings. * * This is called by view.php (to display) and aggregate.php (to process and dispatch). * It returns the basic form with just the submit button by default. Evaluators may * extend or overwrite the default form to include some custom settings. * * @return workshop_evaluation_settings_form */ public function get_settings_form(?moodle_url $actionurl=null) { $customdata = array('workshop' => $this->workshop); $attributes = array('class' => 'evalsettingsform'); return new workshop_evaluation_settings_form($actionurl, $customdata, 'post', '', $attributes); } /** * Delete all data related to a given workshop module instance * * This is called from {@link workshop_delete_instance()}. * * @param int $workshopid id of the workshop module instance being deleted * @return void */ public static function delete_instance($workshopid) { } } /** * Base form to hold eventual evaluation settings. */ class workshop_evaluation_settings_form extends moodleform { /** * Defines the common form fields. */ public function definition() { $mform = $this->_form; $workshop = $this->_customdata['workshop']; $mform->addElement('header', 'general', get_string('evaluationsettings', 'mod_workshop')); $this->definition_sub(); $mform->addElement('submit', 'submit', get_string('aggregategrades', 'workshop')); } /** * Defines the subplugin specific fields. */ protected function definition_sub() { } } best/db/install.xml000064400000002240152267043470010266 0ustar00
best/lang/en/workshopeval_best.php000064400000003504152267043470013312 0ustar00. /** * Strings for component 'workshopeval_best', language 'en', branch 'MOODLE_20_STABLE' * * @package workshopeval * @subpackage best * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['comparison'] = 'Comparison of assessments'; $string['comparison_help'] = 'This setting specifies how strict the comparison of assessments should be. The stricter the comparison, the more similar the assessments need to be in order for a high grade to be obtained.'; $string['comparisonlevel1'] = 'very strict'; $string['comparisonlevel3'] = 'strict'; $string['comparisonlevel5'] = 'fair'; $string['comparisonlevel7'] = 'lax'; $string['comparisonlevel9'] = 'very lax'; $string['configcomparison'] = 'Default value of the factor that influence the grading evaluation.'; $string['pluginname'] = 'Comparison with the best assessment'; $string['privacy:metadata'] = 'The Comparison with the best assessment plugin does not store any personal data. Actual personal data user\'s grades are stored by the Workshop module itself and are attached to the exported submissions and assessments data.'; best/backup/moodle2/restore_workshopeval_best_subplugin.class.php000064400000004467152267043470021545 0ustar00. /** * @package workshopeval * @subpackage best * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * restore subplugin class that provides the necessary information * needed to restore one workshopeval_best subplugin. */ class restore_workshopeval_best_subplugin extends restore_subplugin { //////////////////////////////////////////////////////////////////////////// // mappings of XML paths to the processable methods //////////////////////////////////////////////////////////////////////////// /** * Returns the paths to be handled by the subplugin at workshop level */ protected function define_workshop_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('setting'); $elepath = $this->get_pathfor('/workshopeval_best_settings'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } //////////////////////////////////////////////////////////////////////////// // defined path elements are dispatched to the following methods //////////////////////////////////////////////////////////////////////////// /** * Processes one workshopeval_best_settings element */ public function process_workshopeval_best_setting($data) { global $DB; $data = (object)$data; $data->workshopid = $this->get_new_parentid('workshop'); $DB->insert_record('workshopeval_best_settings', $data); } } best/backup/moodle2/backup_workshopeval_best_subplugin.class.php000064400000004040152267043470021312 0ustar00. /** * @package workshopeval * @subpackage best * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Provides the information to backup grading evaluation method 'Comparison with the best assessment' * * This evaluator just stores a single integer value - the recently used comparison * strictness factor. It adds its XML data to workshop tag. */ class backup_workshopeval_best_subplugin extends backup_subplugin { /** * Returns the subplugin information to attach to workshop element */ protected function define_workshop_subplugin_structure() { // create XML elements $subplugin = $this->get_subplugin_element(); // virtual optigroup element $subplugin_wrapper = new backup_nested_element($this->get_recommended_name()); $subplugin_table_settings = new backup_nested_element('workshopeval_best_settings', null, array('comparison')); // connect XML elements into the tree $subplugin->add_child($subplugin_wrapper); $subplugin_wrapper->add_child($subplugin_table_settings); // set source to populate the data $subplugin_table_settings->set_source_table('workshopeval_best_settings', array('workshopid' => backup::VAR_ACTIVITYID)); return $subplugin; } } best/lib.php000064400000042441152267043470006777 0ustar00. /** * Contains logic class and interface for the grading evaluation plugin "Comparison * with the best assessment". * * @package workshopeval * @subpackage best * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../lib.php'); // interface definition require_once($CFG->libdir . '/gradelib.php'); /** * Defines the computation login of the grading evaluation subplugin */ class workshop_best_evaluation extends workshop_evaluation { /** @var the recently used settings in this workshop */ protected $settings; /** * Constructor * * @param workshop $workshop The workshop api instance * @return void */ public function __construct(workshop $workshop) { global $DB; $this->workshop = $workshop; $this->settings = $DB->get_record('workshopeval_best_settings', array('workshopid' => $this->workshop->id)); } /** * Calculates the grades for assessment and updates 'gradinggrade' fields in 'workshop_assessments' table * * This function relies on the grading strategy subplugin providing get_assessments_recordset() method. * {@see self::process_assessments()} for the required structure of the recordset. * * @param stdClass $settings The settings for this round of evaluation * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewers(s) * * @return void */ public function update_grading_grades(stdclass $settings, $restrict=null) { global $DB; // Remember the recently used settings for this workshop. if (empty($this->settings)) { $record = new stdclass(); $record->workshopid = $this->workshop->id; $record->comparison = $settings->comparison; $DB->insert_record('workshopeval_best_settings', $record); } elseif ($this->settings->comparison != $settings->comparison) { $DB->set_field('workshopeval_best_settings', 'comparison', $settings->comparison, array('workshopid' => $this->workshop->id)); } // Get the grading strategy instance. $grader = $this->workshop->grading_strategy_instance(); // get the information about the assessment dimensions $diminfo = $grader->get_dimensions_info(); // fetch a recordset with all assessments to process $rs = $grader->get_assessments_recordset($restrict); $batch = array(); // will contain a set of all assessments of a single submission $previous = null; // a previous record in the recordset foreach ($rs as $current) { if (is_null($previous)) { // we are processing the very first record in the recordset $previous = $current; } if ($current->submissionid == $previous->submissionid) { $batch[] = $current; } else { // process all the assessments of a single submission $this->process_assessments($batch, $diminfo, $settings); // start with a new batch to be processed $batch = array($current); $previous = $current; } } // do not forget to process the last batch! $this->process_assessments($batch, $diminfo, $settings); $rs->close(); } /** * Returns an instance of the form to provide evaluation settings. * * @return workshop_best_evaluation_settings_form */ public function get_settings_form(?moodle_url $actionurl=null) { $customdata['workshop'] = $this->workshop; $customdata['current'] = $this->settings; $attributes = array('class' => 'evalsettingsform best'); return new workshop_best_evaluation_settings_form($actionurl, $customdata, 'post', '', $attributes); } /** * Delete all data related to a given workshop module instance * * @see workshop_delete_instance() * @param int $workshopid id of the workshop module instance being deleted * @return void */ public static function delete_instance($workshopid) { global $DB; $DB->delete_records('workshopeval_best_settings', array('workshopid' => $workshopid)); } //////////////////////////////////////////////////////////////////////////////// // Internal methods // //////////////////////////////////////////////////////////////////////////////// /** * Given a list of all assessments of a single submission, updates the grading grades in database * * @param array $assessments of stdclass (->assessmentid ->assessmentweight ->reviewerid ->gradinggrade ->submissionid ->dimensionid ->grade) * @param array $diminfo of stdclass (->id ->weight ->max ->min) * @param stdClass grading evaluation settings * @return void */ protected function process_assessments(array $assessments, array $diminfo, stdclass $settings) { global $DB; if (empty($assessments)) { return; } // reindex the passed flat structure to be indexed by assessmentid $assessments = $this->prepare_data_from_recordset($assessments); // normalize the dimension grades to the interval 0 - 100 $assessments = $this->normalize_grades($assessments, $diminfo); // get a hypothetical average assessment $average = $this->average_assessment($assessments); // if unable to calculate the average assessment, set the grading grades to null if (is_null($average)) { foreach ($assessments as $asid => $assessment) { if (!is_null($assessment->gradinggrade)) { $DB->set_field('workshop_assessments', 'gradinggrade', null, array('id' => $asid)); } } return; } // calculate variance of dimension grades $variances = $this->weighted_variance($assessments); foreach ($variances as $dimid => $variance) { $diminfo[$dimid]->variance = $variance; } // for every assessment, calculate its distance from the average one $distances = array(); foreach ($assessments as $asid => $assessment) { $distances[$asid] = $this->assessments_distance($assessment, $average, $diminfo, $settings); } // identify the best assessments - that is those with the shortest distance from the best assessment $bestids = moodle_array_keys_filter($distances, min($distances)); // for every assessment, calculate its distance from the nearest best assessment $distances = array(); foreach ($bestids as $bestid) { $best = $assessments[$bestid]; foreach ($assessments as $asid => $assessment) { $d = $this->assessments_distance($assessment, $best, $diminfo, $settings); if (!is_null($d) and (!isset($distances[$asid]) or $d < $distances[$asid])) { $distances[$asid] = $d; } } } // calculate the grading grade foreach ($distances as $asid => $distance) { $gradinggrade = (100 - $distance); if ($gradinggrade < 0) { $gradinggrade = 0; } if ($gradinggrade > 100) { $gradinggrade = 100; } $grades[$asid] = grade_floatval($gradinggrade); } // if the new grading grade differs from the one stored in database, update it // we do not use set_field() here because we want to pass $bulk param foreach ($grades as $assessmentid => $grade) { if (grade_floats_different($grade, $assessments[$assessmentid]->gradinggrade)) { // the value has changed $record = new stdclass(); $record->id = $assessmentid; $record->gradinggrade = grade_floatval($grade); // do not set timemodified here, it contains the timestamp of when the form was // saved by the peer reviewer, not when it was aggregated $DB->update_record('workshop_assessments', $record, true); // bulk operations expected } } // done. easy, heh? ;-) } /** * Prepares a structure of assessments and given grades * * @param array $assessments batch of recordset items as returned by the grading strategy * @return array */ protected function prepare_data_from_recordset($assessments) { $data = array(); // to be returned foreach ($assessments as $a) { $id = $a->assessmentid; // just an abbreviation if (!isset($data[$id])) { $data[$id] = new stdclass(); $data[$id]->assessmentid = $a->assessmentid; $data[$id]->weight = $a->assessmentweight; $data[$id]->reviewerid = $a->reviewerid; $data[$id]->gradinggrade = $a->gradinggrade; $data[$id]->submissionid = $a->submissionid; $data[$id]->dimgrades = array(); } $data[$id]->dimgrades[$a->dimensionid] = $a->grade; } return $data; } /** * Normalizes the dimension grades to the interval 0.00000 - 100.00000 * * Note: this heavily relies on PHP5 way of handling references in array of stdclasses. Hopefully * it will not change again soon. * * @param array $assessments of stdclass as returned by {@see self::prepare_data_from_recordset()} * @param array $diminfo of stdclass * @return array of stdclass with the same structure as $assessments */ protected function normalize_grades(array $assessments, array $diminfo) { foreach ($assessments as $asid => $assessment) { foreach ($assessment->dimgrades as $dimid => $dimgrade) { $dimmin = $diminfo[$dimid]->min; $dimmax = $diminfo[$dimid]->max; if ($dimmin == $dimmax) { $assessment->dimgrades[$dimid] = grade_floatval($dimmax); } else { $assessment->dimgrades[$dimid] = grade_floatval(($dimgrade - $dimmin) / ($dimmax - $dimmin) * 100); } } } return $assessments; } /** * Given a set of a submission's assessments, returns a hypothetical average assessment * * The passed structure must be array of assessments objects with ->weight and ->dimgrades properties. * Returns null if all passed assessments have zero weight as there is nothing to choose * from then. * * @param array $assessments as prepared by {@link self::prepare_data_from_recordset()} * @return null|stdClass */ protected function average_assessment(array $assessments) { $sumdimgrades = array(); foreach ($assessments as $a) { foreach ($a->dimgrades as $dimid => $dimgrade) { if (!isset($sumdimgrades[$dimid])) { $sumdimgrades[$dimid] = 0; } $sumdimgrades[$dimid] += $dimgrade * $a->weight; } } $sumweights = 0; foreach ($assessments as $a) { $sumweights += $a->weight; } if ($sumweights == 0) { // unable to calculate average assessment return null; } $average = new stdclass(); $average->dimgrades = array(); foreach ($sumdimgrades as $dimid => $sumdimgrade) { $average->dimgrades[$dimid] = grade_floatval($sumdimgrade / $sumweights); } return $average; } /** * Given a set of a submission's assessments, returns standard deviations of all their dimensions * * The passed structure must be array of assessments objects with at least ->weight * and ->dimgrades properties. This implementation uses weighted incremental algorithm as * suggested in "D. H. D. West (1979). Communications of the ACM, 22, 9, 532-535: * Updating Mean and Variance Estimates: An Improved Method" * {@link http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Weighted_incremental_algorithm} * * @param array $assessments as prepared by {@link self::prepare_data_from_recordset()} * @return null|array indexed by dimension id */ protected function weighted_variance(array $assessments) { $first = reset($assessments); if (empty($first)) { return null; } $dimids = array_keys($first->dimgrades); $asids = array_keys($assessments); $vars = array(); // to be returned foreach ($dimids as $dimid) { $n = 0; $s = 0; $sumweight = 0; foreach ($asids as $asid) { $x = $assessments[$asid]->dimgrades[$dimid]; // value (data point) $weight = $assessments[$asid]->weight; // the values' weight if ($weight == 0) { continue; } if ($n == 0) { $n = 1; $mean = $x; $s = 0; $sumweight = $weight; } else { $n++; $temp = $weight + $sumweight; $q = $x - $mean; $r = $q * $weight / $temp; $s = $s + $sumweight * $q * $r; $mean = $mean + $r; $sumweight = $temp; } } if ($sumweight > 0 and $n > 1) { // for the sample: $vars[$dimid] = ($s * $n) / (($n - 1) * $sumweight); // for the population: $vars[$dimid] = $s / $sumweight; } else { $vars[$dimid] = null; } } return $vars; } /** * Measures the distance of the assessment from a referential one * * The passed data structures must contain ->dimgrades property. The referential * assessment is supposed to be close to the average assessment. All dimension grades are supposed to be * normalized to the interval 0 - 100. * Returned value is rounded to 4 valid decimals to prevent some rounding issues - see the unit test * for an example. * * @param stdClass $assessment the assessment being measured * @param stdClass $referential assessment * @param array $diminfo of stdclass(->weight ->min ->max ->variance) indexed by dimension id * @param stdClass $settings * @return float|null rounded to 4 valid decimals */ protected function assessments_distance(stdclass $assessment, stdclass $referential, array $diminfo, stdclass $settings) { $distance = 0; $n = 0; foreach (array_keys($assessment->dimgrades) as $dimid) { $agrade = $assessment->dimgrades[$dimid]; $rgrade = $referential->dimgrades[$dimid]; $var = $diminfo[$dimid]->variance; $weight = $diminfo[$dimid]->weight; $n += $weight; // variations very close to zero are too sensitive to a small change of data values $var = max($var, 0.01); if ($agrade != $rgrade) { $absdelta = abs($agrade - $rgrade); $reldelta = pow($agrade - $rgrade, 2) / ($settings->comparison * $var); $distance += $absdelta * $reldelta * $weight; } } if ($n > 0) { // average distance across all dimensions return round($distance / $n, 4); } else { return null; } } } /** * Represents the settings form for this plugin. */ class workshop_best_evaluation_settings_form extends workshop_evaluation_settings_form { /** * Defines specific fields for this evaluation method. */ protected function definition_sub() { $mform = $this->_form; $plugindefaults = get_config('workshopeval_best'); $current = $this->_customdata['current']; $options = array(); for ($i = 9; $i >= 1; $i = $i-2) { $options[$i] = get_string('comparisonlevel' . $i, 'workshopeval_best'); } $mform->addElement('select', 'comparison', get_string('comparison', 'workshopeval_best'), $options); $mform->addHelpButton('comparison', 'comparison', 'workshopeval_best'); $mform->setDefault('comparison', $plugindefaults->comparison); $this->set_data($current); } } best/version.php000064400000002112152267043470007705 0ustar00. /** * Defines the version of the subplugin * * @package workshopeval * @subpackage best * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'workshopeval_best'; best/classes/privacy/provider.php000064400000002747152267043470013202 0ustar00. /** * Provides the class {@link workshopeval_best\privacy\provider} * * @package workshopeval_best * @category privacy * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopeval_best\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy API implementation for the Comparison with the best assessment method. * * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Explain that this plugin stores no personal data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } best/tests/lib_test.php000064400000033545152267043470011205 0ustar00. /** * Unit tests for grading evaluation method "best" * * @package workshopeval_best * @category test * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopeval_best; use workshop; use workshop_best_evaluation; defined('MOODLE_INTERNAL') || die(); // Include the code to test global $CFG; require_once($CFG->dirroot . '/mod/workshop/locallib.php'); require_once($CFG->dirroot . '/mod/workshop/eval/best/lib.php'); require_once($CFG->libdir . '/gradelib.php'); /** * Unit tests for grading evaluation lib.php */ final class lib_test extends \advanced_testcase { /** workshop instance emulation */ protected $workshop; /** instance of the grading evaluator being tested */ protected $evaluator; /** * Setup testing environment */ protected function setUp(): void { parent::setUp(); $this->resetAfterTest(); $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(); $workshop = $this->getDataGenerator()->create_module('workshop', array('evaluation' => 'best', 'course' => $course)); $cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id]; $this->workshop = new workshop($workshop, $cm, $course); $this->evaluator = new testable_workshop_best_evaluation($this->workshop); } protected function tearDown(): void { $this->workshop = null; $this->evaluator = null; parent::tearDown(); } public function test_normalize_grades(): void { // fixture set-up $assessments = array(); $assessments[1] = (object)array( 'dimgrades' => array(3 => 1.0000, 4 => 13.42300), ); $assessments[3] = (object)array( 'dimgrades' => array(3 => 2.0000, 4 => 19.1000), ); $assessments[7] = (object)array( 'dimgrades' => array(3 => 3.0000, 4 => 0.00000), ); $diminfo = array( 3 => (object)array('min' => 1, 'max' => 3), 4 => (object)array('min' => 0, 'max' => 20), ); // exercise SUT $norm = $this->evaluator->normalize_grades($assessments, $diminfo); // validate $this->assertEquals(gettype($norm), 'array'); // the following grades from a scale $this->assertEquals($norm[1]->dimgrades[3], 0); $this->assertEquals($norm[3]->dimgrades[3], 50); $this->assertEquals($norm[7]->dimgrades[3], 100); // the following grades from an interval 0 - 20 $this->assertEquals($norm[1]->dimgrades[4], grade_floatval(13.423 / 20 * 100)); $this->assertEquals($norm[3]->dimgrades[4], grade_floatval(19.1 / 20 * 100)); $this->assertEquals($norm[7]->dimgrades[4], 0); } public function test_normalize_grades_max_equals_min(): void { // fixture set-up $assessments = array(); $assessments[1] = (object)array( 'dimgrades' => array(3 => 100.0000), ); $diminfo = array( 3 => (object)array('min' => 100, 'max' => 100), ); // exercise SUT $norm = $this->evaluator->normalize_grades($assessments, $diminfo); // validate $this->assertEquals(gettype($norm), 'array'); $this->assertEquals($norm[1]->dimgrades[3], 100); } public function test_average_assessment_same_weights(): void { // fixture set-up $assessments = array(); $assessments[18] = (object)array( 'weight' => 1, 'dimgrades' => array(1 => 50, 2 => 33.33333), ); $assessments[16] = (object)array( 'weight' => 1, 'dimgrades' => array(1 => 0, 2 => 66.66667), ); // exercise SUT $average = $this->evaluator->average_assessment($assessments); // validate $this->assertEquals(gettype($average->dimgrades), 'array'); $this->assertEquals(grade_floatval($average->dimgrades[1]), grade_floatval(25)); $this->assertEquals(grade_floatval($average->dimgrades[2]), grade_floatval(50)); } public function test_average_assessment_different_weights(): void { // fixture set-up $assessments = array(); $assessments[11] = (object)array( 'weight' => 1, 'dimgrades' => array(3 => 10.0, 4 => 13.4, 5 => 95.0), ); $assessments[13] = (object)array( 'weight' => 3, 'dimgrades' => array(3 => 11.0, 4 => 10.1, 5 => 92.0), ); $assessments[17] = (object)array( 'weight' => 1, 'dimgrades' => array(3 => 11.0, 4 => 8.1, 5 => 88.0), ); // exercise SUT $average = $this->evaluator->average_assessment($assessments); // validate $this->assertEquals(gettype($average->dimgrades), 'array'); $this->assertEquals(grade_floatval($average->dimgrades[3]), grade_floatval((10.0 + 11.0*3 + 11.0)/5)); $this->assertEquals(grade_floatval($average->dimgrades[4]), grade_floatval((13.4 + 10.1*3 + 8.1)/5)); $this->assertEquals(grade_floatval($average->dimgrades[5]), grade_floatval((95.0 + 92.0*3 + 88.0)/5)); } public function test_average_assessment_noweight(): void { // fixture set-up $assessments = array(); $assessments[11] = (object)array( 'weight' => 0, 'dimgrades' => array(3 => 10.0, 4 => 13.4, 5 => 95.0), ); $assessments[17] = (object)array( 'weight' => 0, 'dimgrades' => array(3 => 11.0, 4 => 8.1, 5 => 88.0), ); // exercise SUT $average = $this->evaluator->average_assessment($assessments); // validate $this->assertNull($average); } public function test_weighted_variance(): void { // fixture set-up $assessments[11] = (object)array( 'weight' => 1, 'dimgrades' => array(3 => 11, 4 => 2), ); $assessments[13] = (object)array( 'weight' => 3, 'dimgrades' => array(3 => 11, 4 => 4), ); $assessments[17] = (object)array( 'weight' => 2, 'dimgrades' => array(3 => 11, 4 => 5), ); $assessments[20] = (object)array( 'weight' => 1, 'dimgrades' => array(3 => 11, 4 => 7), ); $assessments[25] = (object)array( 'weight' => 1, 'dimgrades' => array(3 => 11, 4 => 9), ); // exercise SUT $variance = $this->evaluator->weighted_variance($assessments); // validate // dimension [3] have all the grades equal to 11 $this->assertEquals($variance[3], 0); // dimension [4] represents data 2, 4, 4, 4, 5, 5, 7, 9 having stdev=2 (stdev is sqrt of variance) $this->assertEquals($variance[4], 4); } public function test_assessments_distance_zero(): void { // fixture set-up $diminfo = array( 3 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567), 4 => (object)array('weight' => 1, 'min' => 1, 'max' => 5, 'variance' => 98.76543), ); $assessment1 = (object)array('dimgrades' => array(3 => 15, 4 => 2)); $assessment2 = (object)array('dimgrades' => array(3 => 15, 4 => 2)); $settings = (object)array('comparison' => 5); // exercise SUT and validate $this->assertEquals($this->evaluator->assessments_distance($assessment1, $assessment2, $diminfo, $settings), 0); } public function test_assessments_distance_equals(): void { /* // fixture set-up $diminfo = array( 3 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567), 4 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567), ); $assessment1 = (object)array('dimgrades' => array(3 => 25, 4 => 4)); $assessment2 = (object)array('dimgrades' => array(3 => 75, 4 => 2)); $referential = (object)array('dimgrades' => array(3 => 50, 4 => 3)); $settings = (object)array('comparison' => 5); // exercise SUT and validate $this->assertEquals($this->evaluator->assessments_distance($assessment1, $referential, $diminfo, $settings), $this->evaluator->assessments_distance($assessment2, $referential, $diminfo, $settings)); */ // fixture set-up $diminfo = array( 1 => (object)array('min' => 0, 'max' => 2, 'weight' => 1, 'variance' => 625), 2 => (object)array('min' => 0, 'max' => 3, 'weight' => 1, 'variance' => 277.7778888889), ); $assessment1 = (object)array('dimgrades' => array(1 => 0, 2 => 66.66667)); $assessment2 = (object)array('dimgrades' => array(1 => 50, 2 => 33.33333)); $referential = (object)array('dimgrades' => array(1 => 25, 2 => 50)); $settings = (object)array('comparison' => 9); // exercise SUT and validate $this->assertEquals($this->evaluator->assessments_distance($assessment1, $referential, $diminfo, $settings), $this->evaluator->assessments_distance($assessment2, $referential, $diminfo, $settings)); } public function test_assessments_distance_zero_variance(): void { // Fixture set-up: an assessment form of the strategy "Number of errors", // three assertions, same weight. $diminfo = array( 1 => (object)array('min' => 0, 'max' => 1, 'weight' => 1), 2 => (object)array('min' => 0, 'max' => 1, 'weight' => 1), 3 => (object)array('min' => 0, 'max' => 1, 'weight' => 1), ); // Simulate structure returned by {@link workshop_best_evaluation::prepare_data_from_recordset()} $assessments = array( // The first assessment has weight 0 and the assessment was No, No, No. 10 => (object)array( 'assessmentid' => 10, 'weight' => 0, 'reviewerid' => 56, 'gradinggrade' => null, 'submissionid' => 99, 'dimgrades' => array( 1 => 0, 2 => 0, 3 => 0, ), ), // The second assessment has weight 1 and assessments was Yes, Yes, Yes. 20 => (object)array( 'assessmentid' => 20, 'weight' => 1, 'reviewerid' => 76, 'gradinggrade' => null, 'submissionid' => 99, 'dimgrades' => array( 1 => 1, 2 => 1, 3 => 1, ), ), // The third assessment has weight 1 and assessments was Yes, Yes, Yes too. 30 => (object)array( 'assessmentid' => 30, 'weight' => 1, 'reviewerid' => 97, 'gradinggrade' => null, 'submissionid' => 99, 'dimgrades' => array( 1 => 1, 2 => 1, 3 => 1, ), ), ); // Process assessments in the same way as in the {@link workshop_best_evaluation::process_assessments()} $assessments = $this->evaluator->normalize_grades($assessments, $diminfo); $average = $this->evaluator->average_assessment($assessments); $variances = $this->evaluator->weighted_variance($assessments); foreach ($variances as $dimid => $variance) { $diminfo[$dimid]->variance = $variance; } // Simulate the chosen comparison of assessments "fair" (does not really matter here but we need something). $settings = (object)array('comparison' => 5); // Exercise SUT: for every assessment, calculate its distance from the average one. $distances = array(); foreach ($assessments as $asid => $assessment) { $distances[$asid] = $this->evaluator->assessments_distance($assessment, $average, $diminfo, $settings); } // Validate: the first assessment is far far away from the average one ... $this->assertTrue($distances[10] > 0); // ... while the two others were both picked as the referential ones. $this->assertTrue($distances[20] == 0); $this->assertTrue($distances[30] == 0); } } /** * Test subclass that makes all the protected methods we want to test public. */ class testable_workshop_best_evaluation extends workshop_best_evaluation { public function normalize_grades(array $assessments, array $diminfo) { return parent::normalize_grades($assessments, $diminfo); } public function average_assessment(array $assessments) { return parent::average_assessment($assessments); } public function weighted_variance(array $assessments) { return parent::weighted_variance($assessments); } public function assessments_distance(\stdClass $assessment, \stdClass $referential, array $diminfo, \stdClass $settings) { return parent::assessments_distance($assessment, $referential, $diminfo, $settings); } } best/settings.php000064400000002574152267043470010074 0ustar00. /** * The configuration variables for "Best" grading evaluation * * The values defined here are used as defaults for all module instances. * * @package workshopeval * @subpackage best * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $options = array(); for ($i = 9; $i >= 1; $i = $i-2) { $options[$i] = get_string('comparisonlevel' . $i, 'workshopeval_best'); } $settings->add(new admin_setting_configselect('workshopeval_best/comparison', get_string('comparison', 'workshopeval_best'), get_string('configcomparison', 'workshopeval_best'), 5, $options));