AutorÃa | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question behaviour type for deferred feedback with CBM behaviour.
*
* @package qbehaviour_deferredcbm
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../deferredfeedback/behaviourtype.php');
/**
* Question behaviour type information for deferred feedback with CBM behaviour.
*
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_deferredcbm_type extends qbehaviour_deferredfeedback_type {
public function adjust_random_guess_score($fraction) {
return question_cbm::adjust_fraction($fraction, question_cbm::default_certainty());
}
public function summarise_usage(question_usage_by_activity $quba, question_display_options $options) {
global $OUTPUT;
$summarydata = parent::summarise_usage($quba, $options);
if ($options->marks < question_display_options::MARK_AND_MAX) {
return $summarydata;
}
// Prepare accumulators to hold the data we are about to collect.
$notansweredcount = 0;
$notansweredweight = 0;
$attemptcount = array(
question_cbm::HIGH => 0,
question_cbm::MED => 0,
question_cbm::LOW => 0,
);
$totalweight = array(
question_cbm::HIGH => 0,
question_cbm::MED => 0,
question_cbm::LOW => 0,
);
$totalrawscore = array(
question_cbm::HIGH => 0,
question_cbm::MED => 0,
question_cbm::LOW => 0,
);
$totalcbmscore = array(
question_cbm::HIGH => 0,
question_cbm::MED => 0,
question_cbm::LOW => 0,
);
// Loop through the data, and add it to the accumulators.
foreach ($quba->get_attempt_iterator() as $qa) {
if (strpos($qa->get_behaviour_name(), 'cbm') === false || $qa->get_max_mark() < 0.0000005) {
continue;
}
$gradedstep = $qa->get_last_step_with_behaviour_var('_rawfraction');
if (!$gradedstep->has_behaviour_var('_rawfraction')) {
$notansweredcount += 1;
$notansweredweight += $qa->get_max_mark();
continue;
}
$certainty = $qa->get_last_behaviour_var('certainty');
if (is_null($certainty) || $certainty == -1) {
// Certainty -1 has never been used in standard Moodle, but is
// used in Tony-Gardiner Medwin's patches to mean 'No idea' which
// we intend to implement: MDL-42077. In the mean time, avoid
// errors for people who have used TGM's patches.
$certainty = question_cbm::default_certainty();
}
$attemptcount[$certainty] += 1;
$totalweight[$certainty] += $qa->get_max_mark();
$totalrawscore[$certainty] += $qa->get_max_mark() * $gradedstep->get_behaviour_var('_rawfraction');
$totalcbmscore[$certainty] += $qa->get_mark();
}
// Hence compute some statistics.
$totalquestions = $notansweredcount + array_sum($attemptcount);
$grandtotalweight = $notansweredweight + array_sum($totalweight);
$accuracy = array_sum($totalrawscore) / $grandtotalweight;
$averagecbm = array_sum($totalcbmscore) / $grandtotalweight;
$cbmbonus = $this->calculate_bonus($averagecbm, $accuracy);
$accuracyandbonus = $accuracy + $cbmbonus;
// Add a note to explain the max mark.
$summarydata['qbehaviour_cbm_grade_explanation'] = array(
'title' => '',
'content' => html_writer::tag('i', get_string('cbmgradeexplanation', 'qbehaviour_deferredcbm')) .
$OUTPUT->help_icon('cbmgrades', 'qbehaviour_deferredcbm'),
);
// Now we can start generating some of the summary: overall values.
$summarydata['qbehaviour_cbm_entire_quiz_heading'] = array(
'title' => '',
'content' => html_writer::tag('h3',
get_string('forentirequiz', 'qbehaviour_deferredcbm', $totalquestions),
array('class' => 'qbehaviour_deferredcbm_summary_heading')),
);
$summarydata['qbehaviour_cbm_entire_quiz_cbm_average'] = array(
'title' => get_string('averagecbmmark', 'qbehaviour_deferredcbm'),
'content' => format_float($averagecbm, $options->markdp),
);
$summarydata['qbehaviour_cbm_entire_quiz_accuracy'] = array(
'title' => get_string('accuracy', 'qbehaviour_deferredcbm'),
'content' => $this->format_probability($accuracy, 1),
);
$summarydata['qbehaviour_cbm_entire_quiz_cbm_bonus'] = array(
'title' => get_string('cbmbonus', 'qbehaviour_deferredcbm'),
'content' => $this->format_probability($cbmbonus, 1),
);
$summarydata['qbehaviour_cbm_entire_quiz_accuracy_and_bonus'] = array(
'title' => get_string('accuracyandbonus', 'qbehaviour_deferredcbm'),
'content' => $this->format_probability($accuracyandbonus, 1),
);
if ($notansweredcount && array_sum($attemptcount) > 0) {
$totalquestions = array_sum($attemptcount);
$grandtotalweight = array_sum($totalweight);
$accuracy = array_sum($totalrawscore) / $grandtotalweight;
$averagecbm = array_sum($totalcbmscore) / $grandtotalweight;
$cbmbonus = $this->calculate_bonus($averagecbm, $accuracy);
$accuracyandbonus = $accuracy + $cbmbonus;
$summarydata['qbehaviour_cbm_answered_quiz_heading'] = array(
'title' => '',
'content' => html_writer::tag('h3',
get_string('foransweredquestions', 'qbehaviour_deferredcbm', $totalquestions),
array('class' => 'qbehaviour_deferredcbm_summary_heading')),
);
$summarydata['qbehaviour_cbm_answered_quiz_cbm_average'] = array(
'title' => get_string('averagecbmmark', 'qbehaviour_deferredcbm'),
'content' => format_float($averagecbm, $options->markdp),
);
$summarydata['qbehaviour_cbm_answered_quiz_accuracy'] = array(
'title' => get_string('accuracy', 'qbehaviour_deferredcbm'),
'content' => $this->format_probability($accuracy, 1),
);
$summarydata['qbehaviour_cbm_answered_quiz_cbm_bonus'] = array(
'title' => get_string('cbmbonus', 'qbehaviour_deferredcbm'),
'content' => $this->format_probability($cbmbonus, 1),
);
$summarydata['qbehaviour_cbm_answered_quiz_accuracy_and_bonus'] = array(
'title' => get_string('accuracyandbonus', 'qbehaviour_deferredcbm'),
'content' => $this->format_probability($accuracyandbonus, 1),
);
}
// Now per-certainty level values.
$summarydata['qbehaviour_cbm_judgement_heading'] = array(
'title' => '',
'content' => html_writer::tag('h3', get_string('breakdownbycertainty', 'qbehaviour_deferredcbm'),
array('class' => 'qbehaviour_deferredcbm_summary_heading')),
);
foreach ($attemptcount as $certainty => $count) {
$key = 'qbehaviour_cbm_judgement' . $certainty;
$title = question_cbm::get_short_string($certainty);
if ($count == 0) {
$summarydata[$key] = array(
'title' => $title,
'content' => get_string('noquestions', 'qbehaviour_deferredcbm'),
);
continue;
}
$lowerlimit = question_cbm::optimal_probablility_low($certainty);
$upperlimit = question_cbm::optimal_probablility_high($certainty);
$fraction = $totalrawscore[$certainty] / $totalweight[$certainty];
$a = new stdClass();
$a->responses = $count;
$a->idealrangelow = $this->format_probability($lowerlimit);
$a->idealrangehigh = $this->format_probability($upperlimit);
$a->fraction = html_writer::tag('span', $this->format_probability($fraction),
array('class' => 'qbehaviour_deferredcbm_actual_percentage'));
if ($fraction < $lowerlimit - 0.0000005) {
if ((pow($fraction - $lowerlimit, 2) * $count) > 0.5) { // Rough indicator of significance: t > 1.5 or 1.8.
$judgement = 'overconfident';
} else {
$judgement = 'slightlyoverconfident';
}
} else if ($fraction > $upperlimit + 0.0000005) {
if ((pow($fraction - $upperlimit, 2) * $count) > 0.5) {
$judgement = 'underconfident';
} else {
$judgement = 'slightlyunderconfident';
}
} else {
$judgement = 'judgementok';
}
$a->judgement = html_writer::tag('span', get_string($judgement, 'qbehaviour_deferredcbm'),
array('class' => 'qbehaviour_deferredcbm_' . $judgement));
$summarydata[$key] = array(
'title' => $title,
'content' => get_string('judgementsummary', 'qbehaviour_deferredcbm', $a),
);
}
return $summarydata;
}
protected function format_probability($probability, $dp = 0) {
return format_float($probability * 100, $dp) . '%';
}
public function calculate_bonus($total, $accuracy) {
$expectedforaccuracy = max(
$accuracy * question_cbm::adjust_fraction(1, question_cbm::LOW) +
(1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::LOW),
$accuracy * question_cbm::adjust_fraction(1, question_cbm::MED) +
(1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::MED),
$accuracy * question_cbm::adjust_fraction(1, question_cbm::HIGH) +
(1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::HIGH)
);
// The constant 0.1 here is determinted empirically from looking at lots
// for CBM quiz results. See www.ucl.ac.uk/~ucgbarg/tea/IUPS_2013a.pdf.
// It approximately maximises the reliability of accuracy + bonus.
return 0.1 * ($total - $expectedforaccuracy);
}
}