MDL-72075 qbank_statistics: Add question statistics to core
This implementation will add the statistics plugin to core. Statistics plugin shows the overall report for a question using a couple columns in the base view.
This commit is contained in:
committed by
Safat Shahin
parent
e5894c0455
commit
341e3079fc
@@ -1958,6 +1958,7 @@ class core_plugin_manager {
|
||||
'importquestions',
|
||||
'managecategories',
|
||||
'previewquestion',
|
||||
'statistics',
|
||||
'tagquestion',
|
||||
'viewcreator',
|
||||
'viewquestionname',
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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/>.
|
||||
|
||||
namespace qbank_statistics\columns;
|
||||
|
||||
use core_question\local\bank\column_base;
|
||||
use qbank_statistics\helper;
|
||||
/**
|
||||
* Discrimination index column
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discrimination_index extends column_base {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function get_title(): string {
|
||||
return get_string('discrimination_index', 'qbank_statistics');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'discrimination_index';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the contents of this column.
|
||||
* @param object $question the row from the $question table, augmented with extra information.
|
||||
* @param string $rowclasses CSS class names that should be applied to this row of output.
|
||||
*/
|
||||
protected function display_content($question, $rowclasses) {
|
||||
global $PAGE;
|
||||
// Average discrimination index per quiz.
|
||||
$discriminationindex = helper::calculate_average_question_discrimination_index($question->id);
|
||||
echo $PAGE->get_renderer('qbank_statistics')->render_discrimination_index($discriminationindex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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/>.
|
||||
|
||||
namespace qbank_statistics\columns;
|
||||
|
||||
use core_question\local\bank\column_base;
|
||||
use qbank_statistics\helper;
|
||||
/**
|
||||
* Discriminative efficiency column
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class discriminative_efficiency extends column_base {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function get_title(): string {
|
||||
return get_string('discriminative_efficiency', 'qbank_statistics');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'discriminative_efficiency';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the contents of this column.
|
||||
* @param object $question the row from the $question table, augmented with extra information.
|
||||
* @param string $rowclasses CSS class names that should be applied to this row of output.
|
||||
*/
|
||||
protected function display_content($question, $rowclasses) {
|
||||
global $PAGE;
|
||||
// Average discriminative efficiency per quiz.
|
||||
$discriminativeefficiency = helper::calculate_average_question_discriminative_efficiency($question->id);
|
||||
echo $PAGE->get_renderer('qbank_statistics')->render_discriminative_efficiency($discriminativeefficiency);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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/>.
|
||||
|
||||
namespace qbank_statistics\columns;
|
||||
|
||||
use core_question\local\bank\column_base;
|
||||
use qbank_statistics\helper;
|
||||
/**
|
||||
* Facility index column
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class facility_index extends column_base {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function get_title(): string {
|
||||
return get_string('facility_index', 'qbank_statistics');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get_name(): string {
|
||||
return 'facility_index';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the contents of this column.
|
||||
* @param object $question the row from the $question table, augmented with extra information.
|
||||
* @param string $rowclasses CSS class names that should be applied to this row of output.
|
||||
*/
|
||||
protected function display_content($question, $rowclasses) {
|
||||
global $PAGE;
|
||||
// Average facility index per quiz.
|
||||
$facility = helper::calculate_average_question_facility($question->id);
|
||||
echo $PAGE->get_renderer('qbank_statistics')->render_facility_index($facility);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?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/>.
|
||||
|
||||
namespace qbank_statistics;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/default.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
|
||||
use core_question\statistics\questions\all_calculated_for_qubaid_condition;
|
||||
use quiz_statistics_report;
|
||||
/**
|
||||
* Helper for statistics
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* @var float Threshold to determine 'need for revision'
|
||||
*/
|
||||
private const NEED_FOR_REVISION_LOWER_THRESHOLD = 30;
|
||||
|
||||
/**
|
||||
* @var float Threshold to determine 'need for revision'
|
||||
*/
|
||||
private const NEED_FOR_REVISION_UPPER_THRESHOLD = 50;
|
||||
|
||||
/**
|
||||
* Return ids of all quizzes that use the question
|
||||
*
|
||||
* @param int $questionid id of the question
|
||||
* @return array list of quizids
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function get_quizzes(int $questionid): array {
|
||||
global $DB;
|
||||
|
||||
$quizzes = $DB->get_fieldset_sql("
|
||||
SELECT DISTINCT qa.quiz as id
|
||||
FROM {quiz_attempts} qa
|
||||
JOIN {question_usages} qu ON qu.id = qa.uniqueid
|
||||
JOIN {question_attempts} qatt ON qatt.questionusageid = qu.id
|
||||
WHERE qatt.questionid = :questionid", ['questionid' => $questionid]);
|
||||
return $quizzes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load question stats from a quiz
|
||||
*
|
||||
* @param int $quizid quiz object or its id
|
||||
* @return all_calculated_for_qubaid_condition
|
||||
*/
|
||||
private static function load_question_stats(int $quizid): all_calculated_for_qubaid_condition {
|
||||
// Turn to quiz object.
|
||||
$quiz = new \stdClass();
|
||||
$quiz->id = $quizid;
|
||||
// All questions, no groups.
|
||||
$report = new quiz_statistics_report();
|
||||
$questions = $report->load_and_initialise_questions_for_calculations($quiz);
|
||||
$qubaids = quiz_statistics_qubaids_condition($quiz->id, new \core\dml\sql_join());
|
||||
$progress = new \core\progress\none();
|
||||
$qcalc = new \core_question\statistics\questions\calculator($questions, $progress);
|
||||
$quizcalc = new \quiz_statistics\calculator($progress);
|
||||
if ($quizcalc->get_last_calculated_time($qubaids) === false) {
|
||||
$questionstats = $qcalc->calculate($qubaids);
|
||||
} else {
|
||||
$questionstats = $qcalc->get_cached($qubaids);
|
||||
}
|
||||
return $questionstats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a specified stats item for a question
|
||||
*
|
||||
* @param int $quizid quiz id
|
||||
* @param int $questionid question id
|
||||
* @param string $item a stats item
|
||||
* @return float|int
|
||||
*/
|
||||
public static function load_question_stats_item(int $quizid, int $questionid, string $item): ?float {
|
||||
$questionstats = self::load_question_stats($quizid);
|
||||
// Find in main question.
|
||||
foreach ($questionstats->questionstats as $stats) {
|
||||
if ($stats->questionid == $questionid && isset($stats->$item)) {
|
||||
return $stats->$item;
|
||||
}
|
||||
}
|
||||
// If not found, find in sub questions.
|
||||
foreach ($questionstats->subquestionstats as $stats) {
|
||||
if ($stats->questionid == $questionid && isset($stats->$item)) {
|
||||
return $stats->$item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate average for a stats item on a question.
|
||||
*
|
||||
* @param int $questionid id of the question
|
||||
* @param string $item stats item
|
||||
* @return float|null
|
||||
*/
|
||||
private static function calculate_average_question_stats_item(int $questionid, string $item): ?float {
|
||||
$quizzes = self::get_quizzes($questionid);
|
||||
|
||||
$sum = 0;
|
||||
$quizcount = count($quizzes);
|
||||
foreach ($quizzes as $quizid) {
|
||||
$value = self::load_question_stats_item($quizid, $questionid, $item);
|
||||
if (!is_null($value)) {
|
||||
$sum += $value;
|
||||
} else {
|
||||
// Exclude this value when it is null.
|
||||
$quizcount--;
|
||||
}
|
||||
}
|
||||
|
||||
// Return null if there is no quizzes.
|
||||
if (empty($quizcount)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Average value per quiz.
|
||||
$average = $sum / $quizcount;
|
||||
return $average;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate average facility index
|
||||
*
|
||||
* @param int $questionid
|
||||
* @return float|null
|
||||
*/
|
||||
public static function calculate_average_question_facility(int $questionid): ?float {
|
||||
return self::calculate_average_question_stats_item($questionid, 'facility');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate average discriminative efficiency
|
||||
*
|
||||
* @param int $questionid question id
|
||||
* @return float|null
|
||||
*/
|
||||
public static function calculate_average_question_discriminative_efficiency(int $questionid): ?float {
|
||||
return self::calculate_average_question_stats_item($questionid, 'discriminativeefficiency');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate average discriminative efficiency
|
||||
*
|
||||
* @param int $questionid question id
|
||||
* @return float|null
|
||||
*/
|
||||
public static function calculate_average_question_discrimination_index(int $questionid): ?float {
|
||||
return self::calculate_average_question_stats_item($questionid, 'discriminationindex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number to a localised percentage with specified decimal points.
|
||||
*
|
||||
* @param float|null $number The number being formatted
|
||||
* @param bool $fraction An indicator for whether the number is a fraction or is already multiplied by 100
|
||||
* @param int $decimals Sets the number of decimal points
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function format_percentage(?float $number, bool $fraction = true, int $decimals = 2): string {
|
||||
if (is_null($number)) {
|
||||
return get_string('na', 'qbank_statistics');
|
||||
}
|
||||
$coefficient = $fraction ? 100 : 1;
|
||||
return get_string('percents', 'moodle', format_float($number * $coefficient, $decimals));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format discrimination index (need for revision).
|
||||
*
|
||||
* @param float|null $value stats value
|
||||
* @return array
|
||||
*/
|
||||
public static function format_discrimination_index(?float $value): array {
|
||||
if (is_null($value) || $value < self::NEED_FOR_REVISION_LOWER_THRESHOLD) {
|
||||
$content = get_string('verylikely', 'qbank_statistics');
|
||||
$classes = 'alert-danger';
|
||||
} else if ($value < self::NEED_FOR_REVISION_UPPER_THRESHOLD) {
|
||||
$content = get_string('likely', 'qbank_statistics');
|
||||
$classes = 'alert-warning';
|
||||
} else {
|
||||
$content = get_string('unlikely', 'qbank_statistics');
|
||||
$classes = 'alert-success';
|
||||
}
|
||||
return [$content, $classes];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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/>.
|
||||
|
||||
namespace qbank_statistics\output;
|
||||
|
||||
use qbank_statistics\helper;
|
||||
/**
|
||||
* Description
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class renderer extends \plugin_renderer_base {
|
||||
|
||||
/**
|
||||
* Render facility index column.
|
||||
*
|
||||
* @param float|null $facility facility index
|
||||
* @return string
|
||||
*/
|
||||
public function render_facility_index(?float $facility): string {
|
||||
$displaydata['facility_index'] = helper::format_percentage($facility);
|
||||
return $this->render_from_template('qbank_statistics/facility_index', $displaydata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render discriminative_efficiency column.
|
||||
*
|
||||
* @param float|null $discriminativeefficiency discriminative efficiency
|
||||
* @return string
|
||||
*/
|
||||
public function render_discriminative_efficiency(?float $discriminativeefficiency): string {
|
||||
$displaydata['discriminative_efficiency'] = helper::format_percentage($discriminativeefficiency, false);
|
||||
return $this->render_from_template('qbank_statistics/discriminative_efficiency', $displaydata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render discrimination index column.
|
||||
*
|
||||
* @param float|null $discriminationindex discrimination index
|
||||
* @return string
|
||||
*/
|
||||
public function render_discrimination_index(?float $discriminationindex): string {
|
||||
list($content, $classes) = helper::format_discrimination_index($discriminationindex);
|
||||
$displaydata['discrimination_index'] = $content;
|
||||
$displaydata['classes'] = $classes;
|
||||
return $this->render_from_template('qbank_statistics/discrimination_index', $displaydata);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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/>.
|
||||
|
||||
namespace qbank_statistics;
|
||||
|
||||
use qbank_statistics\columns\facility_index;
|
||||
use qbank_statistics\columns\discrimination_index;
|
||||
use qbank_statistics\columns\discriminative_efficiency;
|
||||
/**
|
||||
* Class plugin_features is the entrypoint for the columns.
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class plugin_feature extends \core_question\local\bank\plugin_features_base {
|
||||
|
||||
/**
|
||||
* This method will return the array of objects to be rendered as a prt of question bank columns/actions.
|
||||
*
|
||||
* @param view $qbank
|
||||
* @return array
|
||||
*/
|
||||
public function get_question_columns($qbank): array {
|
||||
return [
|
||||
new discrimination_index($qbank),
|
||||
new facility_index($qbank),
|
||||
new discriminative_efficiency($qbank)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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/>.
|
||||
|
||||
namespace qbank_statistics\privacy;
|
||||
|
||||
/**
|
||||
* Privacy Subsystem implementation.
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements \core_privacy\local\metadata\null_provider {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function get_reason(): string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Strings for component qbank_statistics, language 'en'
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['pluginname'] = 'Question statistics';
|
||||
$string['privacy:metadata'] = 'Question statistics plugin does not store any user data.';
|
||||
|
||||
// Columns.
|
||||
$string['facility_index'] = 'Facility index';
|
||||
$string['discriminative_efficiency'] = 'Discriminative efficiency';
|
||||
$string['discrimination_index'] = 'Need for revision';
|
||||
|
||||
// Text format.
|
||||
$string['verylikely'] = 'very likely';
|
||||
$string['likely'] = 'likely';
|
||||
$string['unlikely'] = 'unlikely';
|
||||
$string['na'] = 'N/A';
|
||||
@@ -0,0 +1,31 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template qbank_statistics/discrimination_index
|
||||
Example context (json):
|
||||
{
|
||||
"statistics": [
|
||||
{
|
||||
"discriminative_efficiency": "html content"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
|
||||
<div class="qbank_statistics discrimination_index {{classes}}">
|
||||
{{{discrimination_index}}}
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template qbank_statistics/discriminative_efficiency
|
||||
Example context (json):
|
||||
{
|
||||
"statistics": [
|
||||
{
|
||||
"discriminative_efficiency": "html content"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
|
||||
<div class="qbank_statistics discriminative_efficiency">
|
||||
{{{discriminative_efficiency}}}
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template qbank_statistics/facility_index
|
||||
Example context (json):
|
||||
{
|
||||
"statistics": [
|
||||
{
|
||||
"facility_index": "html content"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
|
||||
<div class="qbank_statistics facility_index">
|
||||
{{{facility_index}}}
|
||||
</div>
|
||||
@@ -0,0 +1,35 @@
|
||||
@qbank @qbank_statistics
|
||||
Feature: Use the qbank plugin manager page for statistics
|
||||
In order to check the plugin behaviour with enable and disable
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber |
|
||||
| quiz | Test quiz | C1 | quiz1 |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | First question | Answer the first question |
|
||||
|
||||
Scenario: Enable/disable statistics columns from the base view
|
||||
Given I log in as "admin"
|
||||
When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
|
||||
And I should see "Question statistics"
|
||||
And I click on "Disable" "link" in the "Question statistics" "table_row"
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
Then I should not see "Facility index"
|
||||
And I should not see "Discriminative efficiency"
|
||||
And I should not see "Need for revision"
|
||||
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
|
||||
And I click on "Enable" "link" in the "Question statistics" "table_row"
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
And I should see "Facility index"
|
||||
And I should see "Discriminative efficiency"
|
||||
And I should see "Need for revision"
|
||||
@@ -0,0 +1,136 @@
|
||||
@qbank @qbank_statistics
|
||||
Feature: Show statistics in question bank
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | user1 | Student1 | student1@example.com |
|
||||
| student2 | user2 | Student2 | student2@example.com |
|
||||
| student3 | user3 | Student3 | student3@example.com |
|
||||
| student4 | user4 | Student4 | student4@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | TF1 | First question |
|
||||
| Test questions | truefalse | TF2 | Second question |
|
||||
| Test questions | truefalse | TF3 | Third question |
|
||||
| Test questions | truefalse | TF4 | Fourth question |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber |
|
||||
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 |
|
||||
| quiz | Quiz 2 | Quiz 2 description | C1 | quiz2 |
|
||||
And quiz "Quiz 1" contains the following questions:
|
||||
| question | page | maxmark |
|
||||
| TF1 | 1 | 1.0 |
|
||||
| TF2 | 1 | 1.0 |
|
||||
| TF3 | 1 | 1.0 |
|
||||
| TF4 | 1 | 1.0 |
|
||||
And quiz "Quiz 2" contains the following questions:
|
||||
| question | page | maxmark |
|
||||
| TF2 | 1 | 1.0 |
|
||||
| TF3 | 1 | 1.0 |
|
||||
And user "student1" has attempted "Quiz 1" with responses:
|
||||
| slot | response |
|
||||
| 1 | False |
|
||||
| 2 | False |
|
||||
| 3 | False |
|
||||
| 4 | False |
|
||||
And user "student2" has attempted "Quiz 1" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
| 2 | True |
|
||||
| 3 | True |
|
||||
| 4 | True |
|
||||
And user "student3" has attempted "Quiz 1" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
| 2 | False |
|
||||
| 3 | False |
|
||||
| 4 | True |
|
||||
And user "student4" has attempted "Quiz 1" with responses:
|
||||
| slot | response |
|
||||
| 1 | False |
|
||||
| 2 | True |
|
||||
| 3 | True |
|
||||
| 4 | False |
|
||||
|
||||
Scenario: View facility index in question bank
|
||||
Given user "student1" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
| 2 | True |
|
||||
And user "student2" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
| 2 | True |
|
||||
When I am on the "Course 1" course page logged in as admin
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
And I should see "50.00%" in the "TF1" "table_row"
|
||||
And I should see "75.00%" in the "TF2" "table_row"
|
||||
And I should see "75.00%" in the "TF3" "table_row"
|
||||
And I should see "50.00%" in the "TF4" "table_row"
|
||||
|
||||
Scenario: View discriminative efficiency in question bank
|
||||
Given user "student1" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | False |
|
||||
| 2 | False |
|
||||
And user "student2" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
| 2 | True |
|
||||
When I am on the "Course 1" course page logged in as admin
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
And I should see "50.00%" in the "TF1" "table_row"
|
||||
And I should see "75.00%" in the "TF2" "table_row"
|
||||
And I should see "75.00%" in the "TF3" "table_row"
|
||||
And I should see "50.00%" in the "TF4" "table_row"
|
||||
|
||||
Scenario: View discrimination index in question bank, good questions
|
||||
Given user "student1" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | False |
|
||||
| 2 | False |
|
||||
And user "student2" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
| 2 | True |
|
||||
When I am on the "Course 1" course page logged in as admin
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
And I should see "likely" in the "TF1" "table_row"
|
||||
And I should see "unlikely" in the "TF2" "table_row"
|
||||
And I should see "unlikely" in the "TF3" "table_row"
|
||||
And I should see "likely" in the "TF4" "table_row"
|
||||
And I should see "likely" in the ".alert-warning" "css_element"
|
||||
And I should see "unlikely" in the ".alert-success" "css_element"
|
||||
|
||||
Scenario: View discrimination index in question bank, bad questions
|
||||
Given user "student1" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
| 2 | True |
|
||||
And user "student2" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | False |
|
||||
| 2 | True |
|
||||
And user "student3" has attempted "Quiz 2" with responses:
|
||||
| slot | response |
|
||||
| 1 | True |
|
||||
| 2 | False |
|
||||
When I am on the "Course 1" course page logged in as admin
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
And I should see "likely" in the "TF1" "table_row"
|
||||
And I should see "very likely" in the "TF2" "table_row"
|
||||
And I should see "very likely" in the "TF3" "table_row"
|
||||
And I should see "likely" in the "TF4" "table_row"
|
||||
And I should see "very likely" in the ".alert-danger" "css_element"
|
||||
And I should see "likely" in the ".alert-warning" "css_element"
|
||||
@@ -0,0 +1,538 @@
|
||||
<?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/>.
|
||||
|
||||
namespace qbank_statistics;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
use quiz;
|
||||
use question_engine;
|
||||
use quiz_attempt;
|
||||
/**
|
||||
* Tests for question statistics.
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test quizzes that contain a specified question.
|
||||
*
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function test_get_quizziess(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
// Create a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Create quizzes.
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$quiz1 = $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'grade' => 100.0, 'sumgrades' => 2,
|
||||
'layout' => '1,2,0'
|
||||
]);
|
||||
$quiz2 = $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'grade' => 100.0, 'sumgrades' => 2,
|
||||
'layout' => '1,2,0'
|
||||
]);
|
||||
$quiz3 = $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'grade' => 100.0, 'sumgrades' => 2,
|
||||
'layout' => '1,2,0'
|
||||
]);
|
||||
$this->assertEquals(3, $DB->count_records('quiz'));
|
||||
|
||||
// Create questions.
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
$question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
|
||||
$question2 = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
|
||||
|
||||
// Add question 1 to quiz 1, 2.
|
||||
quiz_add_quiz_question($question1->id, $quiz1);
|
||||
quiz_add_quiz_question($question1->id, $quiz2);
|
||||
// Quiz 1 attempt.
|
||||
$attempt = ['answer' => 'frog', 'answer' => 10];
|
||||
$this->submit_quiz($quiz1, $attempt);
|
||||
|
||||
// Add question 2 to quiz 2.
|
||||
quiz_add_quiz_question($question2->id, $quiz2);
|
||||
$this->submit_quiz($quiz2, $attempt);
|
||||
|
||||
// Checking quizzes that use question 1.
|
||||
$question1quizzes = helper::get_quizzes($question1->id);
|
||||
$this->assertCount(2, $question1quizzes);
|
||||
$this->assertContains($quiz1->id, $question1quizzes);
|
||||
$this->assertContains($quiz2->id, $question1quizzes);
|
||||
|
||||
// Checking quizzes that contain question 2.
|
||||
$question2quizzes = helper::get_quizzes($question2->id);
|
||||
$this->assertCount(1, $question2quizzes);
|
||||
$this->assertContains($quiz2->id, $question2quizzes);
|
||||
|
||||
// Add random question to quiz3.
|
||||
quiz_add_random_questions($quiz3, 0, $cat->id, 1, false);
|
||||
$this->submit_quiz($quiz3, $attempt);
|
||||
// Quiz 3 will be in one of these arrays.
|
||||
$question1quizzes = helper::get_quizzes($question1->id);
|
||||
$question2quizzes = helper::get_quizzes($question2->id);
|
||||
$this->assertContains($quiz3->id, array_merge($question1quizzes, $question2quizzes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load facility for a question
|
||||
*
|
||||
* @param object $quiz quiz object
|
||||
* @param int $questionid question id
|
||||
* @return float|int
|
||||
*/
|
||||
private function load_question_facility(object $quiz, int $questionid): ?float {
|
||||
return helper::load_question_stats_item($quiz->id, $questionid, 'facility');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load discriminative efficiency for a question
|
||||
*
|
||||
* @param object $quiz quiz object
|
||||
* @param int $questionid question id
|
||||
* @return float|int
|
||||
*/
|
||||
private function load_question_discriminative_efficiency(object $quiz, int $questionid): ?float {
|
||||
return helper::load_question_stats_item($quiz->id, $questionid, 'discriminativeefficiency');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load discrimination index for a question
|
||||
*
|
||||
* @param object $quiz quiz object
|
||||
* @param int $questionid question id
|
||||
* @return float|int
|
||||
*/
|
||||
private function load_question_discrimination_index(object $quiz, int $questionid): ?float {
|
||||
return helper::load_question_stats_item($quiz->id, $questionid, 'discriminationindex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 2 quizzes.
|
||||
*
|
||||
* @return array return 2 quizzes
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
private function prepare_quizzes(): array {
|
||||
// Create a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Make 2 quizzes.
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$layout = '1,2,0,3,4,0';
|
||||
$quiz1 = $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'grade' => 100.0, 'sumgrades' => 2,
|
||||
'layout' => $layout
|
||||
]);
|
||||
|
||||
$quiz2 = $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'grade' => 100.0, 'sumgrades' => 2,
|
||||
'layout' => $layout
|
||||
]);
|
||||
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
|
||||
$page = 1;
|
||||
$questions = [];
|
||||
foreach (explode(',', $layout) as $slot) {
|
||||
if ($slot == 0) {
|
||||
$page += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
$question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
|
||||
$questions[$slot] = $question;
|
||||
quiz_add_quiz_question($question->id, $quiz1, $page);
|
||||
quiz_add_quiz_question($question->id, $quiz2, $page);
|
||||
}
|
||||
|
||||
return [$quiz1, $quiz2, $questions];
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit quiz answers
|
||||
*
|
||||
* @param object $quiz
|
||||
* @param array $answers
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
private function submit_quiz(object $quiz, array $answers): void {
|
||||
// Create user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
// Create attempt.
|
||||
$quizobj = quiz::create($quiz->id, $user->id);
|
||||
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
|
||||
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
|
||||
$timenow = time();
|
||||
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $user->id);
|
||||
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
|
||||
quiz_attempt_save_started($quizobj, $quba, $attempt);
|
||||
// Submit attempt.
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$attemptobj->process_submitted_actions($timenow, false, $answers);
|
||||
$attemptobj->process_finish($timenow, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate attempt answers.
|
||||
*
|
||||
* @param array $correctanswerflags array of 1 or 0
|
||||
* 1 : generate correct answer
|
||||
* 0 : generate wrong answer
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generate_attempt_answers(array $correctanswerflags): array {
|
||||
$attempt = [];
|
||||
for ($i = 1; $i <= 4; $i++) {
|
||||
if (isset($correctanswerflags) && $correctanswerflags[$i - 1] == 1) {
|
||||
// Correct answer.
|
||||
$attempt[$i] = ['answer' => 'frog'];
|
||||
} else {
|
||||
$attempt[$i] = ['answer' => 'false'];
|
||||
}
|
||||
}
|
||||
return $attempt;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Generate quizzes and submit answers.
|
||||
*
|
||||
* @param array $quiz1attempts quiz 1 attempts
|
||||
* @param array $quiz2attempts quiz 2 attempts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepare_and_submit_quizzes(array $quiz1attempts, array $quiz2attempts): array {
|
||||
list($quiz1, $quiz2, $questions) = $this->prepare_quizzes();
|
||||
// Submit attempts of quiz1.
|
||||
foreach ($quiz1attempts as $attempt) {
|
||||
$this->submit_quiz($quiz1, $attempt);
|
||||
}
|
||||
// Submit attempts of quiz2.
|
||||
foreach ($quiz2attempts as $attempt) {
|
||||
$this->submit_quiz($quiz2, $attempt);
|
||||
}
|
||||
return [$quiz1, $quiz2, $questions];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_load_question_facility()}.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function load_question_facility_provider(): \Generator {
|
||||
yield 'Facility case 1' => [
|
||||
'Quiz 1 attempts' => [
|
||||
$this->generate_attempt_answers([1, 0, 0, 0]),
|
||||
],
|
||||
'Expected quiz 1 facilities' => ['100.00%', '0.00%', '0.00%', '0.00%'],
|
||||
'Quiz 2 attempts' => [
|
||||
$this->generate_attempt_answers([1, 0, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 0, 0]),
|
||||
],
|
||||
'Expected quiz 2 facilities' => ['100.00%', '50.00%', '0.00%', '0.00%'],
|
||||
'Expected average facilities' => ['100.00%', '25.00%', '0.00%', '0.00%'],
|
||||
];
|
||||
yield 'Facility case 2' => [
|
||||
'Quiz 1 attempts' => [
|
||||
$this->generate_attempt_answers([1, 0, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 1, 0]),
|
||||
],
|
||||
'Expected quiz 1 facilities' => ['100.00%', '66.67%', '33.33%', '0.00%'],
|
||||
'Quiz 2 attempts' => [
|
||||
$this->generate_attempt_answers([1, 0, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 1, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 1, 1]),
|
||||
],
|
||||
'Expected quiz 2 facilities' => ['100.00%', '75.00%', '50.00%', '25.00%'],
|
||||
'Expected average facilities' => ['100.00%', '70.83%', '41.67%', '12.50%'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test question facility
|
||||
*
|
||||
* @dataProvider load_question_facility_provider
|
||||
*
|
||||
* @param array $quiz1attempts quiz 1 attempts
|
||||
* @param array $expectedquiz1facilities expected quiz 1 facilities
|
||||
* @param array $quiz2attempts quiz 2 attempts
|
||||
* @param array $expectedquiz2facilities expected quiz 2 facilities
|
||||
* @param array $expectedaveragefacilities expected average facilities
|
||||
*/
|
||||
public function test_load_question_facility(array $quiz1attempts, array $expectedquiz1facilities,
|
||||
array $quiz2attempts, array $expectedquiz2facilities,
|
||||
array $expectedaveragefacilities): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
list($quiz1, $quiz2, $questions) = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
|
||||
|
||||
// Quiz 1 facilities.
|
||||
$quiz1facility1 = $this->load_question_facility($quiz1, $questions[1]->id);
|
||||
$quiz1facility2 = $this->load_question_facility($quiz1, $questions[2]->id);
|
||||
$quiz1facility3 = $this->load_question_facility($quiz1, $questions[3]->id);
|
||||
$quiz1facility4 = $this->load_question_facility($quiz1, $questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedquiz1facilities[0], helper::format_percentage($quiz1facility1));
|
||||
$this->assertEquals($expectedquiz1facilities[1], helper::format_percentage($quiz1facility2));
|
||||
$this->assertEquals($expectedquiz1facilities[2], helper::format_percentage($quiz1facility3));
|
||||
$this->assertEquals($expectedquiz1facilities[3], helper::format_percentage($quiz1facility4));
|
||||
|
||||
// Quiz 2 facilities.
|
||||
$quiz2facility1 = $this->load_question_facility($quiz2, $questions[1]->id);
|
||||
$quiz2facility2 = $this->load_question_facility($quiz2, $questions[2]->id);
|
||||
$quiz2facility3 = $this->load_question_facility($quiz2, $questions[3]->id);
|
||||
$quiz2facility4 = $this->load_question_facility($quiz2, $questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedquiz2facilities[0], helper::format_percentage($quiz2facility1));
|
||||
$this->assertEquals($expectedquiz2facilities[1], helper::format_percentage($quiz2facility2));
|
||||
$this->assertEquals($expectedquiz2facilities[2], helper::format_percentage($quiz2facility3));
|
||||
$this->assertEquals($expectedquiz2facilities[3], helper::format_percentage($quiz2facility4));
|
||||
|
||||
// Average question facilities.
|
||||
$averagefacility1 = helper::calculate_average_question_facility($questions[1]->id);
|
||||
$averagefacility2 = helper::calculate_average_question_facility($questions[2]->id);
|
||||
$averagefacility3 = helper::calculate_average_question_facility($questions[3]->id);
|
||||
$averagefacility4 = helper::calculate_average_question_facility($questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedaveragefacilities[0], helper::format_percentage($averagefacility1));
|
||||
$this->assertEquals($expectedaveragefacilities[1], helper::format_percentage($averagefacility2));
|
||||
$this->assertEquals($expectedaveragefacilities[2], helper::format_percentage($averagefacility3));
|
||||
$this->assertEquals($expectedaveragefacilities[3], helper::format_percentage($averagefacility4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_load_question_discriminative_efficiency()}.
|
||||
* @return \Generator
|
||||
*/
|
||||
public function load_question_discriminative_efficiency_provider() {
|
||||
yield 'Discriminative efficiency' => [
|
||||
'Quiz 1 attempts' => [
|
||||
$this->generate_attempt_answers([1, 0, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 0, 1, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 1, 1]),
|
||||
],
|
||||
'Expected quiz 1 discriminative efficiency' => ['N/A', '33.33%', '33.33%', '100.00%'],
|
||||
'Quiz 2 attempts' => [
|
||||
$this->generate_attempt_answers([1, 1, 1, 1]),
|
||||
$this->generate_attempt_answers([0, 0, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 0, 0, 1]),
|
||||
$this->generate_attempt_answers([0, 1, 1, 0]),
|
||||
],
|
||||
'Expected quiz 2 discriminative efficiency' => ['50.00%', '50.00%', '50.00%', '50.00%'],
|
||||
'Expected average discriminative efficiency' => ['50.00%', '41.67%', '41.67%', '75.00%'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test discriminative efficiency
|
||||
*
|
||||
* @dataProvider load_question_discriminative_efficiency_provider
|
||||
*
|
||||
* @param array $quiz1attempts quiz 1 attempts
|
||||
* @param array $expectedquiz1discriminativeefficiency expected quiz 1 discriminative efficiency
|
||||
* @param array $quiz2attempts quiz 2 attempts
|
||||
* @param array $expectedquiz2discriminativeefficiency expected quiz 2 discriminative efficiency
|
||||
* @param array $expectedaveragediscriminativeefficiency expected average discriminative efficiency
|
||||
*/
|
||||
public function test_load_question_discriminative_efficiency(array $quiz1attempts, array $expectedquiz1discriminativeefficiency,
|
||||
array $quiz2attempts, array $expectedquiz2discriminativeefficiency,
|
||||
array $expectedaveragediscriminativeefficiency): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
list($quiz1, $quiz2, $questions) = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
|
||||
|
||||
// Quiz 1 discriminative efficiency.
|
||||
$discriminativeefficiency1 = $this->load_question_discriminative_efficiency($quiz1, $questions[1]->id);
|
||||
$discriminativeefficiency2 = $this->load_question_discriminative_efficiency($quiz1, $questions[2]->id);
|
||||
$discriminativeefficiency3 = $this->load_question_discriminative_efficiency($quiz1, $questions[3]->id);
|
||||
$discriminativeefficiency4 = $this->load_question_discriminative_efficiency($quiz1, $questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedquiz1discriminativeefficiency[0],
|
||||
helper::format_percentage($discriminativeefficiency1, false),
|
||||
"Failure in quiz 1 - question 1 discriminative efficiency");
|
||||
$this->assertEquals($expectedquiz1discriminativeefficiency[1],
|
||||
helper::format_percentage($discriminativeefficiency2, false),
|
||||
"Failure in quiz 1 - question 2 discriminative efficiency");
|
||||
$this->assertEquals($expectedquiz1discriminativeefficiency[2],
|
||||
helper::format_percentage($discriminativeefficiency3, false),
|
||||
"Failure in quiz 1 - question 3 discriminative efficiency");
|
||||
$this->assertEquals($expectedquiz1discriminativeefficiency[3],
|
||||
helper::format_percentage($discriminativeefficiency4, false),
|
||||
"Failure in quiz 1 - question 4 discriminative efficiency");
|
||||
|
||||
// Quiz 2 discriminative efficiency.
|
||||
$discriminativeefficiency1 = $this->load_question_discriminative_efficiency($quiz2, $questions[1]->id);
|
||||
$discriminativeefficiency2 = $this->load_question_discriminative_efficiency($quiz2, $questions[2]->id);
|
||||
$discriminativeefficiency3 = $this->load_question_discriminative_efficiency($quiz2, $questions[3]->id);
|
||||
$discriminativeefficiency4 = $this->load_question_discriminative_efficiency($quiz2, $questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedquiz2discriminativeefficiency[0],
|
||||
helper::format_percentage($discriminativeefficiency1, false),
|
||||
"Failure in quiz 2 - question 1 discriminative efficiency");
|
||||
$this->assertEquals($expectedquiz2discriminativeefficiency[1],
|
||||
helper::format_percentage($discriminativeefficiency2, false),
|
||||
"Failure in quiz 2 - question 2 discriminative efficiency");
|
||||
$this->assertEquals($expectedquiz2discriminativeefficiency[2],
|
||||
helper::format_percentage($discriminativeefficiency3, false),
|
||||
"Failure in quiz 2 - question 3 discriminative efficiency");
|
||||
$this->assertEquals($expectedquiz2discriminativeefficiency[3],
|
||||
helper::format_percentage($discriminativeefficiency4, false),
|
||||
"Failure in quiz 2 - question 4 discriminative efficiency");
|
||||
|
||||
// Average question discriminative efficiency.
|
||||
$avgdiscriminativeefficiency1 = helper::calculate_average_question_discriminative_efficiency($questions[1]->id);
|
||||
$avgdiscriminativeefficiency2 = helper::calculate_average_question_discriminative_efficiency($questions[2]->id);
|
||||
$avgdiscriminativeefficiency3 = helper::calculate_average_question_discriminative_efficiency($questions[3]->id);
|
||||
$avgdiscriminativeefficiency4 = helper::calculate_average_question_discriminative_efficiency($questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedaveragediscriminativeefficiency[0],
|
||||
helper::format_percentage($avgdiscriminativeefficiency1, false),
|
||||
"Failure in question 1 average discriminative efficiency");
|
||||
$this->assertEquals($expectedaveragediscriminativeefficiency[1],
|
||||
helper::format_percentage($avgdiscriminativeefficiency2, false),
|
||||
"Failure in question 2 average discriminative efficiency");
|
||||
$this->assertEquals($expectedaveragediscriminativeefficiency[2],
|
||||
helper::format_percentage($avgdiscriminativeefficiency3, false),
|
||||
"Failure in question 3 average discriminative efficiency");
|
||||
$this->assertEquals($expectedaveragediscriminativeefficiency[3],
|
||||
helper::format_percentage($avgdiscriminativeefficiency4, false),
|
||||
"Failure in question 4 average discriminative efficiency");
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_load_question_discrimination_index()}.
|
||||
* @return \Generator
|
||||
*/
|
||||
public function load_question_discrimination_index_provider() {
|
||||
yield 'Discrimination Index' => [
|
||||
'Quiz 1 attempts' => [
|
||||
$this->generate_attempt_answers([1, 0, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 0, 1, 0]),
|
||||
$this->generate_attempt_answers([1, 1, 1, 1]),
|
||||
],
|
||||
'Expected quiz 1 Discrimination Index' => ['N/A', '30.15%', '30.15%', '81.65%'],
|
||||
'Quiz 2 attempts' => [
|
||||
$this->generate_attempt_answers([1, 1, 1, 1]),
|
||||
$this->generate_attempt_answers([0, 0, 0, 0]),
|
||||
$this->generate_attempt_answers([1, 0, 0, 1]),
|
||||
$this->generate_attempt_answers([0, 1, 1, 0]),
|
||||
],
|
||||
'Expected quiz 2 discrimination Index' => ['44.72%', '44.72%', '44.72%', '44.72%'],
|
||||
'Expected average discrimination Index' => ['44.72%', '37.44%', '37.44%', '63.19%'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test discrimination index
|
||||
*
|
||||
* @dataProvider load_question_discrimination_index_provider
|
||||
*
|
||||
* @param array $quiz1attempts quiz 1 attempts
|
||||
* @param array $expectedquiz1discriminationindex expected quiz 1 discrimination index
|
||||
* @param array $quiz2attempts quiz 2 attempts
|
||||
* @param array $expectedquiz2discriminationindex expected quiz 2 discrimination index
|
||||
* @param array $expectedaveragediscriminationindex expected average discrimination index
|
||||
*/
|
||||
public function test_load_question_discrimination_index(array $quiz1attempts, array $expectedquiz1discriminationindex,
|
||||
array $quiz2attempts, array $expectedquiz2discriminationindex,
|
||||
array $expectedaveragediscriminationindex): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
list($quiz1, $quiz2, $questions) = $this->prepare_and_submit_quizzes($quiz1attempts, $quiz2attempts);
|
||||
|
||||
// Quiz 1 discrimination index.
|
||||
$discriminationindex1 = $this->load_question_discrimination_index($quiz1, $questions[1]->id);
|
||||
$discriminationindex2 = $this->load_question_discrimination_index($quiz1, $questions[2]->id);
|
||||
$discriminationindex3 = $this->load_question_discrimination_index($quiz1, $questions[3]->id);
|
||||
$discriminationindex4 = $this->load_question_discrimination_index($quiz1, $questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedquiz1discriminationindex[0],
|
||||
helper::format_percentage($discriminationindex1, false),
|
||||
"Failure in quiz 1 - question 1 discrimination index");
|
||||
$this->assertEquals($expectedquiz1discriminationindex[1],
|
||||
helper::format_percentage($discriminationindex2, false),
|
||||
"Failure in quiz 1 - question 2 discrimination index");
|
||||
$this->assertEquals($expectedquiz1discriminationindex[2],
|
||||
helper::format_percentage($discriminationindex3, false),
|
||||
"Failure in quiz 1 - question 3 discrimination index");
|
||||
$this->assertEquals($expectedquiz1discriminationindex[3],
|
||||
helper::format_percentage($discriminationindex4, false),
|
||||
"Failure in quiz 1 - question 4 discrimination index");
|
||||
|
||||
// Quiz 2 discrimination index.
|
||||
$discriminationindex1 = $this->load_question_discrimination_index($quiz2, $questions[1]->id);
|
||||
$discriminationindex2 = $this->load_question_discrimination_index($quiz2, $questions[2]->id);
|
||||
$discriminationindex3 = $this->load_question_discrimination_index($quiz2, $questions[3]->id);
|
||||
$discriminationindex4 = $this->load_question_discrimination_index($quiz2, $questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedquiz2discriminationindex[0],
|
||||
helper::format_percentage($discriminationindex1, false),
|
||||
"Failure in quiz 2 - question 1 discrimination index");
|
||||
$this->assertEquals($expectedquiz2discriminationindex[1],
|
||||
helper::format_percentage($discriminationindex2, false),
|
||||
"Failure in quiz 2 - question 2 discrimination index");
|
||||
$this->assertEquals($expectedquiz2discriminationindex[2],
|
||||
helper::format_percentage($discriminationindex3, false),
|
||||
"Failure in quiz 2 - question 3 discrimination index");
|
||||
$this->assertEquals($expectedquiz2discriminationindex[3],
|
||||
helper::format_percentage($discriminationindex4, false),
|
||||
"Failure in quiz 2 - question 4 discrimination index");
|
||||
|
||||
// Average question discrimination index.
|
||||
$avgdiscriminationindex1 = helper::calculate_average_question_discrimination_index($questions[1]->id);
|
||||
$avgdiscriminationindex2 = helper::calculate_average_question_discrimination_index($questions[2]->id);
|
||||
$avgdiscriminationindex3 = helper::calculate_average_question_discrimination_index($questions[3]->id);
|
||||
$avgdiscriminationindex4 = helper::calculate_average_question_discrimination_index($questions[4]->id);
|
||||
|
||||
$this->assertEquals($expectedaveragediscriminationindex[0],
|
||||
helper::format_percentage($avgdiscriminationindex1, false),
|
||||
"Failure in question 1 average discrimination index");
|
||||
$this->assertEquals($expectedaveragediscriminationindex[1],
|
||||
helper::format_percentage($avgdiscriminationindex2, false),
|
||||
"Failure in question 2 average discrimination index");
|
||||
$this->assertEquals($expectedaveragediscriminationindex[2],
|
||||
helper::format_percentage($avgdiscriminationindex3, false),
|
||||
"Failure in question 3 average discrimination index");
|
||||
$this->assertEquals($expectedaveragediscriminationindex[3],
|
||||
helper::format_percentage($avgdiscriminationindex4, false),
|
||||
"Failure in question 4 average discrimination index");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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 statistics
|
||||
*
|
||||
* @package qbank_statistics
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Nathan Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'qbank_statistics';
|
||||
$plugin->version = 2021092000;
|
||||
$plugin->requires = 2021052500;
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
Reference in New Issue
Block a user