1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Question behaviour type for deferred feedback with CBM behaviour. 19 * 20 * @package qbehaviour_deferredcbm 21 * @copyright 2012 The Open University 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 require_once (__DIR__ . '/../deferredfeedback/behaviourtype.php'); 29 30 31 /** 32 * Question behaviour type information for deferred feedback with CBM behaviour. 33 * 34 * @copyright 2012 The Open University 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class qbehaviour_deferredcbm_type extends qbehaviour_deferredfeedback_type { 38 public function adjust_random_guess_score($fraction) { 39 return question_cbm::adjust_fraction($fraction, question_cbm::default_certainty()); 40 } 41 42 public function summarise_usage(question_usage_by_activity $quba, question_display_options $options) { 43 global $OUTPUT; 44 $summarydata = parent::summarise_usage($quba, $options); 45 46 if ($options->marks < question_display_options::MARK_AND_MAX) { 47 return $summarydata; 48 } 49 50 // Prepare accumulators to hold the data we are about to collect. 51 $notansweredcount = 0; 52 $notansweredweight = 0; 53 $attemptcount = array( 54 question_cbm::HIGH => 0, 55 question_cbm::MED => 0, 56 question_cbm::LOW => 0, 57 ); 58 $totalweight = array( 59 question_cbm::HIGH => 0, 60 question_cbm::MED => 0, 61 question_cbm::LOW => 0, 62 ); 63 $totalrawscore = array( 64 question_cbm::HIGH => 0, 65 question_cbm::MED => 0, 66 question_cbm::LOW => 0, 67 ); 68 $totalcbmscore = array( 69 question_cbm::HIGH => 0, 70 question_cbm::MED => 0, 71 question_cbm::LOW => 0, 72 ); 73 74 // Loop through the data, and add it to the accumulators. 75 foreach ($quba->get_attempt_iterator() as $qa) { 76 if (strpos($qa->get_behaviour_name(), 'cbm') === false || $qa->get_max_mark() < 0.0000005) { 77 continue; 78 } 79 80 $gradedstep = $qa->get_last_step_with_behaviour_var('_rawfraction'); 81 82 if (!$gradedstep->has_behaviour_var('_rawfraction')) { 83 $notansweredcount += 1; 84 $notansweredweight += $qa->get_max_mark(); 85 continue; 86 } 87 88 $certainty = $qa->get_last_behaviour_var('certainty'); 89 if (is_null($certainty) || $certainty == -1) { 90 // Certainty -1 has never been used in standard Moodle, but is 91 // used in Tony-Gardiner Medwin's patches to mean 'No idea' which 92 // we intend to implement: MDL-42077. In the mean time, avoid 93 // errors for people who have used TGM's patches. 94 $certainty = question_cbm::default_certainty(); 95 } 96 97 $attemptcount[$certainty] += 1; 98 $totalweight[$certainty] += $qa->get_max_mark(); 99 $totalrawscore[$certainty] += $qa->get_max_mark() * $gradedstep->get_behaviour_var('_rawfraction'); 100 $totalcbmscore[$certainty] += $qa->get_mark(); 101 } 102 103 // Hence compute some statistics. 104 $totalquestions = $notansweredcount + array_sum($attemptcount); 105 $grandtotalweight = $notansweredweight + array_sum($totalweight); 106 $accuracy = array_sum($totalrawscore) / $grandtotalweight; 107 $averagecbm = array_sum($totalcbmscore) / $grandtotalweight; 108 $cbmbonus = $this->calculate_bonus($averagecbm, $accuracy); 109 $accuracyandbonus = $accuracy + $cbmbonus; 110 111 // Add a note to explain the max mark. 112 $summarydata['qbehaviour_cbm_grade_explanation'] = array( 113 'title' => '', 114 'content' => html_writer::tag('i', get_string('cbmgradeexplanation', 'qbehaviour_deferredcbm')) . 115 $OUTPUT->help_icon('cbmgrades', 'qbehaviour_deferredcbm'), 116 ); 117 118 // Now we can start generating some of the summary: overall values. 119 $summarydata['qbehaviour_cbm_entire_quiz_heading'] = array( 120 'title' => '', 121 'content' => html_writer::tag('h3', 122 get_string('forentirequiz', 'qbehaviour_deferredcbm', $totalquestions), 123 array('class' => 'qbehaviour_deferredcbm_summary_heading')), 124 ); 125 $summarydata['qbehaviour_cbm_entire_quiz_cbm_average'] = array( 126 'title' => get_string('averagecbmmark', 'qbehaviour_deferredcbm'), 127 'content' => format_float($averagecbm, $options->markdp), 128 ); 129 $summarydata['qbehaviour_cbm_entire_quiz_accuracy'] = array( 130 'title' => get_string('accuracy', 'qbehaviour_deferredcbm'), 131 'content' => $this->format_probability($accuracy, 1), 132 ); 133 $summarydata['qbehaviour_cbm_entire_quiz_cbm_bonus'] = array( 134 'title' => get_string('cbmbonus', 'qbehaviour_deferredcbm'), 135 'content' => $this->format_probability($cbmbonus, 1), 136 ); 137 $summarydata['qbehaviour_cbm_entire_quiz_accuracy_and_bonus'] = array( 138 'title' => get_string('accuracyandbonus', 'qbehaviour_deferredcbm'), 139 'content' => $this->format_probability($accuracyandbonus, 1), 140 ); 141 142 if ($notansweredcount && array_sum($attemptcount) > 0) { 143 $totalquestions = array_sum($attemptcount); 144 $grandtotalweight = array_sum($totalweight); 145 $accuracy = array_sum($totalrawscore) / $grandtotalweight; 146 $averagecbm = array_sum($totalcbmscore) / $grandtotalweight; 147 $cbmbonus = $this->calculate_bonus($averagecbm, $accuracy); 148 $accuracyandbonus = $accuracy + $cbmbonus; 149 150 $summarydata['qbehaviour_cbm_answered_quiz_heading'] = array( 151 'title' => '', 152 'content' => html_writer::tag('h3', 153 get_string('foransweredquestions', 'qbehaviour_deferredcbm', $totalquestions), 154 array('class' => 'qbehaviour_deferredcbm_summary_heading')), 155 ); 156 $summarydata['qbehaviour_cbm_answered_quiz_cbm_average'] = array( 157 'title' => get_string('averagecbmmark', 'qbehaviour_deferredcbm'), 158 'content' => format_float($averagecbm, $options->markdp), 159 ); 160 $summarydata['qbehaviour_cbm_answered_quiz_accuracy'] = array( 161 'title' => get_string('accuracy', 'qbehaviour_deferredcbm'), 162 'content' => $this->format_probability($accuracy, 1), 163 ); 164 $summarydata['qbehaviour_cbm_answered_quiz_cbm_bonus'] = array( 165 'title' => get_string('cbmbonus', 'qbehaviour_deferredcbm'), 166 'content' => $this->format_probability($cbmbonus, 1), 167 ); 168 $summarydata['qbehaviour_cbm_answered_quiz_accuracy_and_bonus'] = array( 169 'title' => get_string('accuracyandbonus', 'qbehaviour_deferredcbm'), 170 'content' => $this->format_probability($accuracyandbonus, 1), 171 ); 172 } 173 174 // Now per-certainty level values. 175 $summarydata['qbehaviour_cbm_judgement_heading'] = array( 176 'title' => '', 177 'content' => html_writer::tag('h3', get_string('breakdownbycertainty', 'qbehaviour_deferredcbm'), 178 array('class' => 'qbehaviour_deferredcbm_summary_heading')), 179 ); 180 181 foreach ($attemptcount as $certainty => $count) { 182 $key = 'qbehaviour_cbm_judgement' . $certainty; 183 $title = question_cbm::get_short_string($certainty); 184 185 if ($count == 0) { 186 $summarydata[$key] = array( 187 'title' => $title, 188 'content' => get_string('noquestions', 'qbehaviour_deferredcbm'), 189 ); 190 continue; 191 } 192 193 $lowerlimit = question_cbm::optimal_probablility_low($certainty); 194 $upperlimit = question_cbm::optimal_probablility_high($certainty); 195 $fraction = $totalrawscore[$certainty] / $totalweight[$certainty]; 196 197 $a = new stdClass(); 198 $a->responses = $count; 199 $a->idealrangelow = $this->format_probability($lowerlimit); 200 $a->idealrangehigh = $this->format_probability($upperlimit); 201 $a->fraction = html_writer::tag('span', $this->format_probability($fraction), 202 array('class' => 'qbehaviour_deferredcbm_actual_percentage')); 203 204 if ($fraction < $lowerlimit - 0.0000005) { 205 if ((pow($fraction - $lowerlimit, 2) * $count) > 0.5) { // Rough indicator of significance: t > 1.5 or 1.8. 206 $judgement = 'overconfident'; 207 } else { 208 $judgement = 'slightlyoverconfident'; 209 } 210 } else if ($fraction > $upperlimit + 0.0000005) { 211 if ((pow($fraction - $upperlimit, 2) * $count) > 0.5) { 212 $judgement = 'underconfident'; 213 } else { 214 $judgement = 'slightlyunderconfident'; 215 } 216 } else { 217 $judgement = 'judgementok'; 218 } 219 $a->judgement = html_writer::tag('span', get_string($judgement, 'qbehaviour_deferredcbm'), 220 array('class' => 'qbehaviour_deferredcbm_' . $judgement)); 221 222 $summarydata[$key] = array( 223 'title' => $title, 224 'content' => get_string('judgementsummary', 'qbehaviour_deferredcbm', $a), 225 ); 226 } 227 228 return $summarydata; 229 } 230 231 protected function format_probability($probability, $dp = 0) { 232 return format_float($probability * 100, $dp) . '%'; 233 } 234 235 public function calculate_bonus($total, $accuracy) { 236 $expectedforaccuracy = max( 237 $accuracy * question_cbm::adjust_fraction(1, question_cbm::LOW) + 238 (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::LOW), 239 $accuracy * question_cbm::adjust_fraction(1, question_cbm::MED) + 240 (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::MED), 241 $accuracy * question_cbm::adjust_fraction(1, question_cbm::HIGH) + 242 (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::HIGH) 243 ); 244 // The constant 0.1 here is determinted empirically from looking at lots 245 // for CBM quiz results. See www.ucl.ac.uk/~ucgbarg/tea/IUPS_2013a.pdf. 246 // It approximately maximises the reliability of accuracy + bonus. 247 return 0.1 * ($total - $expectedforaccuracy); 248 } 249 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body