MDL-59211 analytics: Make cibot happy
Part of MDL-57791 epic.
This commit is contained in:
+1
-1
@@ -81,4 +81,4 @@ theme/boost/scss/fontawesome/
|
||||
theme/bootstrapbase/less/bootstrap/
|
||||
theme/bootstrapbase/javascript/html5shiv.js
|
||||
theme/bootstrapbase/amd/src/bootstrap.js
|
||||
theme/bootstrapbase/less/fontawesome/
|
||||
theme/bootstrapbase/less/fontawesome/
|
||||
+1
-1
@@ -82,4 +82,4 @@ theme/boost/scss/fontawesome/
|
||||
theme/bootstrapbase/less/bootstrap/
|
||||
theme/bootstrapbase/javascript/html5shiv.js
|
||||
theme/bootstrapbase/amd/src/bootstrap.js
|
||||
theme/bootstrapbase/less/fontawesome/
|
||||
theme/bootstrapbase/less/fontawesome/
|
||||
@@ -13,11 +13,12 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/notification'], functi
|
||||
|
||||
/**
|
||||
* Prepares a modal info for a log's results.
|
||||
*
|
||||
* @access public
|
||||
* @param {int} id
|
||||
* @return {String} HTML info
|
||||
* @param {string[]} info
|
||||
*/
|
||||
loadInfo : function(id, info) {
|
||||
loadInfo: function(id, info) {
|
||||
|
||||
var link = $('[data-model-log-id="' + id + '"]');
|
||||
str.get_string('loginfo', 'tool_models').then(function(langString) {
|
||||
|
||||
@@ -40,12 +40,22 @@ require_once($CFG->dirroot . '/completion/completion_completion.php');
|
||||
*/
|
||||
class course_dropout extends \core_analytics\local\target\binary {
|
||||
|
||||
protected $coursegradeitem = null;
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('target:coursedropout', 'tool_models');
|
||||
}
|
||||
|
||||
/**
|
||||
* prediction_actions
|
||||
*
|
||||
* @param \core_analytics\prediction $prediction
|
||||
* @param bool $includedetailsaction
|
||||
* @return \core_analytics\prediction_action[]
|
||||
*/
|
||||
public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false) {
|
||||
global $USER;
|
||||
|
||||
@@ -57,17 +67,24 @@ class course_dropout extends \core_analytics\local\target\binary {
|
||||
// Send a message.
|
||||
$url = new \moodle_url('/message/index.php', array('user' => $USER->id, 'id' => $studentid));
|
||||
$pix = new \pix_icon('t/message', get_string('sendmessage', 'message'));
|
||||
$actions['studentmessage'] = new \core_analytics\prediction_action('studentmessage', $prediction, $url, $pix, get_string('sendmessage', 'message'));
|
||||
$actions['studentmessage'] = new \core_analytics\prediction_action('studentmessage', $prediction, $url, $pix,
|
||||
get_string('sendmessage', 'message'));
|
||||
|
||||
// View outline report.
|
||||
$url = new \moodle_url('/report/outline/user.php', array('id' => $studentid, 'course' => $sampledata['course']->id,
|
||||
'mode' => 'outline'));
|
||||
$pix = new \pix_icon('i/report', get_string('outlinereport'));
|
||||
$actions['viewoutlinereport'] = new \core_analytics\prediction_action('viewoutlinereport', $prediction, $url, $pix, get_string('outlinereport'));
|
||||
$actions['viewoutlinereport'] = new \core_analytics\prediction_action('viewoutlinereport', $prediction, $url, $pix,
|
||||
get_string('outlinereport'));
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* classes_description
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected static function classes_description() {
|
||||
return array(
|
||||
get_string('labelstudentdropoutno', 'tool_models'),
|
||||
@@ -86,10 +103,22 @@ class course_dropout extends \core_analytics\local\target\binary {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* get_analyser_class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_analyser_class() {
|
||||
return '\\core_analytics\\local\\analyser\\student_enrolments';
|
||||
}
|
||||
|
||||
/**
|
||||
* is_valid_analysable
|
||||
*
|
||||
* @param \core_analytics\analysable $course
|
||||
* @param bool $fortraining
|
||||
* @return true|string
|
||||
*/
|
||||
public function is_valid_analysable(\core_analytics\analysable $course, $fortraining = true) {
|
||||
global $DB;
|
||||
|
||||
@@ -145,7 +174,7 @@ class course_dropout extends \core_analytics\local\target\binary {
|
||||
* @param int $sampleid
|
||||
* @param \core_analytics\analysable $course
|
||||
* @param bool $fortraining
|
||||
* @return bool
|
||||
* @return true|string
|
||||
*/
|
||||
public function is_valid_sample($sampleid, \core_analytics\analysable $course, $fortraining = true) {
|
||||
return true;
|
||||
@@ -160,7 +189,9 @@ class course_dropout extends \core_analytics\local\target\binary {
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param \core_analytics\analysable $course
|
||||
* @return void
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
|
||||
|
||||
|
||||
@@ -44,10 +44,22 @@ class no_teaching extends \core_analytics\local\target\binary {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('target:noteachingactivity', 'tool_models');
|
||||
}
|
||||
|
||||
/**
|
||||
* prediction_actions
|
||||
*
|
||||
* @param \core_analytics\prediction $prediction
|
||||
* @param mixed $includedetailsaction
|
||||
* @return \core_analytics\prediction_action[]
|
||||
*/
|
||||
public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false) {
|
||||
global $USER;
|
||||
|
||||
@@ -79,6 +91,11 @@ class no_teaching extends \core_analytics\local\target\binary {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* classes_description
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected static function classes_description() {
|
||||
return array(
|
||||
get_string('labelteachingyes', 'tool_models'),
|
||||
@@ -96,10 +113,22 @@ class no_teaching extends \core_analytics\local\target\binary {
|
||||
return array(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_analyser_class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_analyser_class() {
|
||||
return '\\core_analytics\\local\\analyser\\site_courses';
|
||||
}
|
||||
|
||||
/**
|
||||
* is_valid_analysable
|
||||
*
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param mixed $fortraining
|
||||
* @return true|string
|
||||
*/
|
||||
public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true) {
|
||||
// The analysable is the site, so yes, it is always valid.
|
||||
return true;
|
||||
@@ -108,10 +137,10 @@ class no_teaching extends \core_analytics\local\target\binary {
|
||||
/**
|
||||
* Only process samples which start date is getting close.
|
||||
*
|
||||
* @param mixed $sampleid
|
||||
* @param int $sampleid
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param bool $fortraining
|
||||
* @return void
|
||||
* @return true|string
|
||||
*/
|
||||
public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true) {
|
||||
|
||||
@@ -132,7 +161,9 @@ class no_teaching extends \core_analytics\local\target\binary {
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @return void
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) {
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
namespace tool_models\output\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot.'/lib/formslib.php');
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,10 @@
|
||||
|
||||
namespace tool_models\output;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Typical crappy helper class with tiny functions.
|
||||
* Helper class with general purpose tiny functions.
|
||||
*
|
||||
* @package tool_models
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -33,6 +35,12 @@ namespace tool_models\output;
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* Converts a class full name to a select option key
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
public static function class_to_option($class) {
|
||||
// Form field is PARAM_ALPHANUMEXT and we are sending fully qualified class names
|
||||
// as option names, but replacing the backslash for a string that is really unlikely
|
||||
@@ -40,6 +48,12 @@ class helper {
|
||||
return str_replace('\\', '2015102400ouuu', $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* option_to_class
|
||||
*
|
||||
* @param string $option
|
||||
* @return string
|
||||
*/
|
||||
public static function option_to_class($option) {
|
||||
// Really unlikely but yeah, I'm a bad booyyy.
|
||||
return str_replace('2015102400ouuu', '\\', $option);
|
||||
|
||||
@@ -35,8 +35,19 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class models_list implements \renderable, \templatable {
|
||||
|
||||
/**
|
||||
* models
|
||||
*
|
||||
* @var \core_analytics\model[]
|
||||
*/
|
||||
protected $models = array();
|
||||
|
||||
/**
|
||||
* __construct
|
||||
*
|
||||
* @param \core_analytics\model[] $models
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($models) {
|
||||
$this->models = $models;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ class renderer extends plugin_renderer_base {
|
||||
|
||||
if (isset($result->score)) {
|
||||
// Score.
|
||||
$output .= $OUTPUT->heading(get_string('accuracy', 'tool_models') . ': ' . round(floatval($result->score), 4) * 100 . '%', 4);
|
||||
$output .= $OUTPUT->heading(get_string('accuracy', 'tool_models') . ': ' .
|
||||
round(floatval($result->score), 4) * 100 . '%', 4);
|
||||
}
|
||||
|
||||
if (!empty($result->info)) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
@@ -25,6 +24,8 @@
|
||||
|
||||
namespace tool_models\task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Predict system models with new data available.
|
||||
*
|
||||
@@ -34,10 +35,20 @@ namespace tool_models\task;
|
||||
*/
|
||||
class predict_models extends \core\task\scheduled_task {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('predictmodels', 'tool_models');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the prediction task.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute() {
|
||||
global $OUTPUT, $PAGE;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
@@ -25,6 +24,8 @@
|
||||
|
||||
namespace tool_models\task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Train system models with new data available.
|
||||
*
|
||||
@@ -34,10 +35,20 @@ namespace tool_models\task;
|
||||
*/
|
||||
class train_models extends \core\task\scheduled_task {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('trainmodels', 'tool_models');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the prediction task.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute() {
|
||||
global $OUTPUT, $PAGE;
|
||||
|
||||
|
||||
@@ -101,6 +101,13 @@ foreach ($courses as $course) {
|
||||
$courses->close();
|
||||
|
||||
|
||||
/**
|
||||
* tool_models_calculate_course_dates
|
||||
*
|
||||
* @param stdClass $course
|
||||
* @param array $options CLI options
|
||||
* @return void
|
||||
*/
|
||||
function tool_models_calculate_course_dates($course, $options) {
|
||||
global $DB, $OUTPUT;
|
||||
|
||||
@@ -153,7 +160,8 @@ function tool_models_calculate_course_dates($course, $options) {
|
||||
if ($options['update']) {
|
||||
format_weeks::update_end_date($course->id);
|
||||
$course->enddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
|
||||
$notification .= PHP_EOL . ' ' . get_string('weeksenddateautomaticallyset', 'tool_models') . ': ' . userdate($course->enddate);
|
||||
$notification .= PHP_EOL . ' ' . get_string('weeksenddateautomaticallyset', 'tool_models') . ': ' .
|
||||
userdate($course->enddate);
|
||||
} else {
|
||||
// We can't provide more info without actually updating it in db.
|
||||
$notification .= PHP_EOL . ' ' . get_string('weeksenddatedefault', 'tool_models');
|
||||
@@ -194,4 +202,6 @@ function tool_models_calculate_course_dates($course, $options) {
|
||||
echo mtrace($notification);
|
||||
}
|
||||
|
||||
echo mtrace(get_string('success'));
|
||||
|
||||
exit(0);
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
/**
|
||||
* This file defines tasks performed by the tool.
|
||||
*
|
||||
* @package tool_monitor
|
||||
* @copyright 2014 Mark Nelson <markn@moodle.com>
|
||||
* @package tool_models
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
// List of tasks.
|
||||
$tasks = array(
|
||||
array(
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Deletes models created by tool_models
|
||||
*
|
||||
* @package tool_models
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
function xmldb_tool_models_uninstall() {
|
||||
global $DB;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
|
||||
admin_externalpage_setup('analyticmodels', '', null, '', array('pagelayout'=>'report'));
|
||||
admin_externalpage_setup('analyticmodels', '', null, '', array('pagelayout' => 'report'));
|
||||
|
||||
$models = \core_analytics\manager::get_all_models();
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
/**
|
||||
* Model-related actions.
|
||||
*
|
||||
* @package tool_model
|
||||
* @package tool_models
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Extension to show an error message if the selected predictor is not available.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -27,6 +28,13 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/../../lib/adminlib.php');
|
||||
|
||||
/**
|
||||
* Extension to show an error message if the selected predictor is not available.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class admin_setting_predictor extends \admin_setting_configselect {
|
||||
|
||||
/**
|
||||
@@ -40,7 +48,7 @@ class admin_setting_predictor extends \admin_setting_configselect {
|
||||
return '';
|
||||
}
|
||||
if (!array_key_exists($data, $this->choices)) {
|
||||
return ''; // ignore it
|
||||
return '';
|
||||
}
|
||||
|
||||
// Calling it here without checking if it is ready because we check it below and show it as a controlled case.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Any element analysers can analyse.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -26,6 +27,7 @@ namespace core_analytics;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Any element analysers can analyse.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -33,13 +35,36 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
interface analysable {
|
||||
|
||||
/**
|
||||
* Max timestamp.
|
||||
*/
|
||||
const MAX_TIME = 9999999999;
|
||||
|
||||
/**
|
||||
* The analysable unique identifier in the site.
|
||||
*
|
||||
* @return int.
|
||||
*/
|
||||
public function get_id();
|
||||
|
||||
/**
|
||||
* The analysable context.
|
||||
*
|
||||
* @return \context
|
||||
*/
|
||||
public function get_context();
|
||||
|
||||
/**
|
||||
* The start of the analysable if there is one.
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function get_start();
|
||||
|
||||
/**
|
||||
* The end of the analysable if there is one.
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function get_end();
|
||||
}
|
||||
|
||||
@@ -35,10 +35,29 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class calculable {
|
||||
|
||||
/**
|
||||
* Neutral calculation outcome.
|
||||
*/
|
||||
const OUTCOME_NEUTRAL = 0;
|
||||
|
||||
/**
|
||||
* Very positive calculation outcome.
|
||||
*/
|
||||
const OUTCOME_VERY_POSITIVE = 1;
|
||||
|
||||
/**
|
||||
* Positive calculation outcome.
|
||||
*/
|
||||
const OUTCOME_OK = 2;
|
||||
|
||||
/**
|
||||
* Negative calculation outcome.
|
||||
*/
|
||||
const OUTCOME_NEGATIVE = 3;
|
||||
|
||||
/**
|
||||
* Very negative calculation outcome.
|
||||
*/
|
||||
const OUTCOME_VERY_NEGATIVE = 4;
|
||||
|
||||
/**
|
||||
@@ -243,7 +262,6 @@ abstract class calculable {
|
||||
* Merges arrays recursively keeping the same keys the original arrays have.
|
||||
*
|
||||
* @link http://php.net/manual/es/function.array-merge-recursive.php#114818
|
||||
* @param array
|
||||
* @return array
|
||||
*/
|
||||
private function array_merge_recursive_keep_keys() {
|
||||
@@ -256,8 +274,8 @@ abstract class calculable {
|
||||
if (is_array($value) && !empty($base[$key]) && is_array($base[$key])) {
|
||||
$base[$key] = $this->array_merge_recursive_keep_keys($base[$key], $value);
|
||||
} else {
|
||||
if(isset($base[$key]) && is_int($key)) {
|
||||
$key++;
|
||||
if (isset($base[$key]) && is_int($key)) {
|
||||
$key++;
|
||||
}
|
||||
$base[$key] = $value;
|
||||
}
|
||||
|
||||
+131
-16
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Moodle course analysable
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -30,6 +31,7 @@ require_once($CFG->dirroot . '/lib/gradelib.php');
|
||||
require_once($CFG->dirroot . '/lib/enrollib.php');
|
||||
|
||||
/**
|
||||
* Moodle course analysable
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -37,24 +39,81 @@ require_once($CFG->dirroot . '/lib/enrollib.php');
|
||||
*/
|
||||
class course implements \core_analytics\analysable {
|
||||
|
||||
/**
|
||||
* @var \core_analytics\course[] $instances
|
||||
*/
|
||||
protected static $instances = array();
|
||||
|
||||
protected static $studentroles = [];
|
||||
protected static $teacherroles = [];
|
||||
|
||||
/**
|
||||
* Course object
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
protected $course = null;
|
||||
|
||||
/**
|
||||
* The course context.
|
||||
*
|
||||
* @var \context_course
|
||||
*/
|
||||
protected $coursecontext = null;
|
||||
|
||||
/**
|
||||
* The course activities organized by activity type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $courseactivities = array();
|
||||
|
||||
/**
|
||||
* Course start time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $starttime = null;
|
||||
|
||||
|
||||
/**
|
||||
* Has the course already started?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $started = null;
|
||||
|
||||
/**
|
||||
* Course end time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $endtime = null;
|
||||
|
||||
/**
|
||||
* Is the course finished?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $finished = null;
|
||||
|
||||
/**
|
||||
* Course students ids.
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
protected $studentids = [];
|
||||
|
||||
|
||||
/**
|
||||
* Course teachers ids
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
protected $teacherids = [];
|
||||
|
||||
/**
|
||||
* Cached copy of the total number of logs in the course.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $ntotallogs = null;
|
||||
|
||||
/**
|
||||
@@ -78,18 +137,14 @@ class course implements \core_analytics\analysable {
|
||||
|
||||
$this->coursecontext = \context_course::instance($this->course->id);
|
||||
|
||||
if (empty(self::$studentroles)) {
|
||||
self::$studentroles = array_keys(get_archetype_roles('student'));
|
||||
}
|
||||
if (empty(self::$teacherroles)) {
|
||||
self::$teacherroles = array_keys(get_archetype_roles('editingteacher') + get_archetype_roles('teacher'));
|
||||
}
|
||||
|
||||
$this->now = time();
|
||||
|
||||
// Get the course users, including users assigned to student and teacher roles at an higher context.
|
||||
$this->studentids = $this->get_user_ids(self::$studentroles);
|
||||
$this->teacherids = $this->get_user_ids(self::$teacherroles);
|
||||
$studentroles = array_keys(get_archetype_roles('student'));
|
||||
$this->studentids = $this->get_user_ids($studentroles);
|
||||
|
||||
$teacherroles = array_keys(get_archetype_roles('editingteacher') + get_archetype_roles('teacher'));
|
||||
$this->teacherids = $this->get_user_ids($teacherroles);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,10 +169,20 @@ class course implements \core_analytics\analysable {
|
||||
return self::$instances[$courseid];
|
||||
}
|
||||
|
||||
/**
|
||||
* get_id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->course->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_context
|
||||
*
|
||||
* @return \context
|
||||
*/
|
||||
public function get_context() {
|
||||
if ($this->coursecontext === null) {
|
||||
$this->coursecontext = \context_course::instance($this->course->id);
|
||||
@@ -147,6 +212,11 @@ class course implements \core_analytics\analysable {
|
||||
return $this->starttime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses the start of the course based on students' activity and enrolment start dates.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function guess_start() {
|
||||
global $DB;
|
||||
|
||||
@@ -168,8 +238,10 @@ class course implements \core_analytics\analysable {
|
||||
$select = "userid = :userid AND contextlevel = :contextlevel AND contextinstanceid = :contextinstanceid";
|
||||
$params = array('userid' => $studentid, 'contextlevel' => CONTEXT_COURSE, 'contextinstanceid' => $this->get_id());
|
||||
$events = $logstore->get_events_select($select, $params, 'timecreated ASC', 0, 1);
|
||||
$event = reset($events);
|
||||
$firstlogs = $event->timecreated;
|
||||
if ($events) {
|
||||
$event = reset($events);
|
||||
$firstlogs[] = $event->timecreated;
|
||||
}
|
||||
}
|
||||
if (empty($firstlogs)) {
|
||||
// Can't guess if no student accesses.
|
||||
@@ -257,6 +329,11 @@ class course implements \core_analytics\analysable {
|
||||
return $this->median($studentlastaccesses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a course plain object.
|
||||
*
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function get_course_data() {
|
||||
return $this->course;
|
||||
}
|
||||
@@ -362,6 +439,12 @@ class course implements \core_analytics\analysable {
|
||||
return $this->ntotallogs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the activities of the provided type the course has.
|
||||
*
|
||||
* @param string $activitytype
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_activities($activitytype) {
|
||||
|
||||
// Using is set because we set it to false if there are no activities.
|
||||
@@ -383,6 +466,12 @@ class course implements \core_analytics\analysable {
|
||||
return $this->courseactivities[$activitytype];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the course students grades.
|
||||
*
|
||||
* @param array $courseactivities
|
||||
* @return array
|
||||
*/
|
||||
public function get_student_grades($courseactivities) {
|
||||
|
||||
if (empty($courseactivities)) {
|
||||
@@ -410,9 +499,18 @@ class course implements \core_analytics\analysable {
|
||||
return $grades;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses all activities that were available during a period of time.
|
||||
*
|
||||
* @param string $activitytype
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @param \stdClass $student
|
||||
* @return array
|
||||
*/
|
||||
public function get_activities($activitytype, $starttime, $endtime, $student = false) {
|
||||
|
||||
// $student may not be available, default to not calculating dynamic data.
|
||||
// Var $student may not be available, default to not calculating dynamic data.
|
||||
$studentid = -1;
|
||||
if ($student) {
|
||||
$studentid = $student->id;
|
||||
@@ -432,6 +530,14 @@ class course implements \core_analytics\analysable {
|
||||
return $timerangeactivities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Was the activity supposed to be completed during the provided time range?.
|
||||
*
|
||||
* @param \cm_info $activity
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return bool
|
||||
*/
|
||||
protected function completed_by(\cm_info $activity, $starttime, $endtime) {
|
||||
|
||||
// We can't check uservisible because:
|
||||
@@ -455,7 +561,7 @@ class course implements \core_analytics\analysable {
|
||||
}
|
||||
}
|
||||
|
||||
//// We skip activities in sections that were not yet visible or their 'until' was not in this $starttime - $endtime range.
|
||||
// We skip activities in sections that were not yet visible or their 'until' was not in this $starttime - $endtime range.
|
||||
$section = $activity->get_modinfo()->get_section_info($activity->sectionnum);
|
||||
if ($section->availability) {
|
||||
$info = new \core_availability\info_section($section);
|
||||
@@ -524,6 +630,14 @@ class course implements \core_analytics\analysable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the activity/section should have been completed during the provided period according to its availability rules.
|
||||
*
|
||||
* @param \core_availability\info $info
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return bool|null
|
||||
*/
|
||||
protected function availability_completed_by(\core_availability\info $info, $starttime, $endtime) {
|
||||
|
||||
$dateconditions = $info->get_availability_tree()->get_all_children('\availability_date\condition');
|
||||
@@ -576,6 +690,7 @@ class course implements \core_analytics\analysable {
|
||||
/**
|
||||
* Returns the query and params used to filter the logstore by this course students.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @return array
|
||||
*/
|
||||
protected function course_students_query_filter($prefix = false) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Datasets manager.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -26,6 +27,7 @@ namespace core_analytics;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Datasets manager.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -33,8 +35,19 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class dataset_manager {
|
||||
|
||||
/**
|
||||
* File area for labelled datasets.
|
||||
*/
|
||||
const LABELLED_FILEAREA = 'labelled';
|
||||
|
||||
/**
|
||||
* File area for unlabelled datasets.
|
||||
*/
|
||||
const UNLABELLED_FILEAREA = 'unlabelled';
|
||||
|
||||
/**
|
||||
* Evaluation file file name.
|
||||
*/
|
||||
const EVALUATION_FILENAME = 'evaluation.csv';
|
||||
|
||||
/**
|
||||
@@ -71,8 +84,13 @@ class dataset_manager {
|
||||
protected $includetarget;
|
||||
|
||||
/**
|
||||
* Simple constructor.
|
||||
* Constructor method.
|
||||
*
|
||||
* @param int $modelid
|
||||
* @param int $analysableid
|
||||
* @param string $timesplittingid
|
||||
* @param bool $evaluation
|
||||
* @param bool $includetarget
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($modelid, $analysableid, $timesplittingid, $evaluation = false, $includetarget = false) {
|
||||
@@ -90,7 +108,8 @@ class dataset_manager {
|
||||
*/
|
||||
public function init_process() {
|
||||
$lockkey = 'modelid:' . $this->modelid . '-analysableid:' . $this->analysableid .
|
||||
'-timesplitting:' . self::clean_time_splitting_id($this->timesplittingid) . '-includetarget:' . (int)$this->includetarget;
|
||||
'-timesplitting:' . self::clean_time_splitting_id($this->timesplittingid) .
|
||||
'-includetarget:' . (int)$this->includetarget;
|
||||
|
||||
// Large timeout as processes may be quite long.
|
||||
$lockfactory = \core\lock\lock_config::get_lock_factory('core_analytics');
|
||||
@@ -168,6 +187,13 @@ class dataset_manager {
|
||||
'/timesplitting/' . self::clean_time_splitting_id($timesplittingid) . '/', self::EVALUATION_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes previous evaluation files of this model.
|
||||
*
|
||||
* @param int $modelid
|
||||
* @param string $timesplittingid
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete_previous_evaluation_file($modelid, $timesplittingid) {
|
||||
$fs = get_file_storage();
|
||||
if ($file = self::get_previous_evaluation_file($modelid, $timesplittingid)) {
|
||||
@@ -178,6 +204,14 @@ class dataset_manager {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this (model + analysable + time splitting) file.
|
||||
*
|
||||
* @param int $modelid
|
||||
* @param int $analysableid
|
||||
* @param string $timesplittingid
|
||||
* @return \stored_file
|
||||
*/
|
||||
public static function get_evaluation_analysable_file($modelid, $analysableid, $timesplittingid) {
|
||||
|
||||
// Delete previous file if it exists.
|
||||
@@ -196,7 +230,6 @@ class dataset_manager {
|
||||
* Important! It is the caller responsability to ensure that the datasets are compatible.
|
||||
*
|
||||
* @param array $files
|
||||
* @param string $filename
|
||||
* @param int $modelid
|
||||
* @param string $timesplittingid
|
||||
* @param bool $evaluation
|
||||
@@ -277,6 +310,12 @@ class dataset_manager {
|
||||
return $fs->create_file_from_pathname($filerecord, $tmpfilepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dataset file data structured by sampleids using the indicators and target column names.
|
||||
*
|
||||
* @param \stored_file $dataset
|
||||
* @return array
|
||||
*/
|
||||
public static function get_structured_data(\stored_file $dataset) {
|
||||
|
||||
if ($dataset->get_filearea() !== 'unlabelled') {
|
||||
@@ -315,6 +354,12 @@ class dataset_manager {
|
||||
return $calculations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all files of a model.
|
||||
*
|
||||
* @param int $modelid
|
||||
* @return bool
|
||||
*/
|
||||
public static function clear_model_files($modelid) {
|
||||
$fs = get_file_storage();
|
||||
return $fs->delete_area_files(\context_system::instance()->id, 'analytics', false, $modelid);
|
||||
@@ -331,6 +376,12 @@ class dataset_manager {
|
||||
return clean_param($timesplittingid, PARAM_ALPHANUMEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file name to be used.
|
||||
*
|
||||
* @param strinbool $evaluation
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_filename($evaluation) {
|
||||
|
||||
if ($evaluation === true) {
|
||||
@@ -343,6 +394,12 @@ class dataset_manager {
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file area to be used.
|
||||
*
|
||||
* @param bool $includetarget
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_filearea($includetarget) {
|
||||
|
||||
if ($includetarget === true) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Analysers base class.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -26,6 +27,7 @@ namespace core_analytics\local\analyser;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Analysers base class.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -33,16 +35,59 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class base {
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $modelid;
|
||||
|
||||
/**
|
||||
* The model target.
|
||||
*
|
||||
* @var \core_analytics\local\target\base
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* The model indicators.
|
||||
*
|
||||
* @var \core_analytics\local\indicator\base[]
|
||||
*/
|
||||
protected $indicators;
|
||||
|
||||
/**
|
||||
* Time splitting methods to use.
|
||||
*
|
||||
* Multiple time splitting methods during evaluation and 1 single
|
||||
* time splitting method once the model is enabled.
|
||||
*
|
||||
* @var \core_analytics\local\time_splitting\base[]
|
||||
*/
|
||||
protected $timesplittings;
|
||||
|
||||
/**
|
||||
* Execution options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Simple log array.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $log;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*
|
||||
* @param int $modelid
|
||||
* @param \core_analytics\local\target\base $target
|
||||
* @param \core_analytics\local\indicator\base[] $indicators
|
||||
* @param \core_analytics\local\time_splitting\base[] $timesplittings
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($modelid, \core_analytics\local\target\base $target, $indicators, $timesplittings, $options) {
|
||||
$this->modelid = $modelid;
|
||||
$this->target = $target;
|
||||
@@ -61,7 +106,7 @@ abstract class base {
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns the list of samples that can be calculated.
|
||||
* This function returns this analysable list of samples.
|
||||
*
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata)
|
||||
@@ -69,7 +114,7 @@ abstract class base {
|
||||
abstract protected function get_all_samples(\core_analytics\analysable $analysable);
|
||||
|
||||
/**
|
||||
* get_samples
|
||||
* This function returns the samples data from a list of sample ids.
|
||||
*
|
||||
* @param int[] $sampleids
|
||||
* @return array array[0] = int[] (sampleids) and array[1] = array (samplesdata)
|
||||
@@ -77,7 +122,7 @@ abstract class base {
|
||||
abstract public function get_samples($sampleids);
|
||||
|
||||
/**
|
||||
* get_sample_analysable
|
||||
* Returns the analysable of a sample.
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @return \core_analytics\analysable
|
||||
@@ -85,13 +130,15 @@ abstract class base {
|
||||
abstract public function get_sample_analysable($sampleid);
|
||||
|
||||
/**
|
||||
* get_samples_origin
|
||||
* Returns the sample's origin in moodle database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_samples_origin();
|
||||
|
||||
/**
|
||||
* Returns the context of a sample.
|
||||
*
|
||||
* moodle/analytics:listinsights will be required at this level to access the sample predictions.
|
||||
*
|
||||
* @param int $sampleid
|
||||
@@ -100,7 +147,7 @@ abstract class base {
|
||||
abstract public function sample_access_context($sampleid);
|
||||
|
||||
/**
|
||||
* sample_description
|
||||
* Describes a sample with a description summary and a \renderable (an image for example)
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param int $contextid
|
||||
@@ -109,10 +156,6 @@ abstract class base {
|
||||
*/
|
||||
abstract public function sample_description($sampleid, $contextid, $sampledata);
|
||||
|
||||
protected function provided_sample_data() {
|
||||
return array($this->get_samples_origin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Main analyser method which processes the site analysables.
|
||||
*
|
||||
@@ -121,14 +164,34 @@ abstract class base {
|
||||
* In most of the cases you should have enough extending from one of these classes so you don't need
|
||||
* to reimplement this method.
|
||||
*
|
||||
* @param bool $includetarget
|
||||
* @return \stored_file[]
|
||||
*/
|
||||
abstract public function get_analysable_data($includetarget);
|
||||
|
||||
/**
|
||||
* Samples data this analyser provides.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function provided_sample_data() {
|
||||
return array($this->get_samples_origin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns labelled data (training and evaluation).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_labelled_data() {
|
||||
return $this->get_analysable_data(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unlabelled data (prediction).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_unlabelled_data() {
|
||||
return $this->get_analysable_data(false);
|
||||
}
|
||||
@@ -151,7 +214,7 @@ abstract class base {
|
||||
}
|
||||
|
||||
/**
|
||||
* check_indicator_requirements
|
||||
* Checks that this analyser satisfies the provided indicator requirements.
|
||||
*
|
||||
* @param \core_analytics\local\indicator\base $indicator
|
||||
* @return true|string[] True if all good, missing requirements list otherwise
|
||||
@@ -248,7 +311,7 @@ abstract class base {
|
||||
|
||||
$a = new \stdClass();
|
||||
$a->analysableid = $analysable->get_id();
|
||||
$a->errors = implode(', ', $errors);
|
||||
$a->errors = implode(', ', $errors);
|
||||
$this->add_log(get_string('analysablenotused', 'analytics', $a));
|
||||
}
|
||||
|
||||
@@ -256,7 +319,7 @@ abstract class base {
|
||||
}
|
||||
|
||||
/**
|
||||
* add_log
|
||||
* Adds a register to the analysis log.
|
||||
*
|
||||
* @param string $string
|
||||
* @return void
|
||||
@@ -266,7 +329,7 @@ abstract class base {
|
||||
}
|
||||
|
||||
/**
|
||||
* get_logs
|
||||
* Returns the analysis logs.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
@@ -274,12 +337,20 @@ abstract class base {
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the analysable samples using the provided time splitting method.
|
||||
*
|
||||
* @param \core_analytics\local\time_splitting\base $timesplitting
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param \core_analytics\local\target\base|false $target
|
||||
* @return \stdClass Results object.
|
||||
*/
|
||||
protected function process_time_splitting($timesplitting, $analysable, $target = false) {
|
||||
|
||||
$result = new \stdClass();
|
||||
|
||||
if (!$timesplitting->is_valid_analysable($analysable)) {
|
||||
$result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR;
|
||||
$result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD;
|
||||
$result->message = get_string('invalidanalysablefortimesplitting', 'analytics',
|
||||
$timesplitting->get_name());
|
||||
return $result;
|
||||
@@ -287,7 +358,8 @@ abstract class base {
|
||||
$timesplitting->set_analysable($analysable);
|
||||
|
||||
if (CLI_SCRIPT && !PHPUNIT_TEST) {
|
||||
mtrace('Analysing id "' . $analysable->get_id() . '" with "' . $timesplitting->get_name() . '" time splitting method...');
|
||||
mtrace('Analysing id "' . $analysable->get_id() . '" with "' . $timesplitting->get_name() .
|
||||
'" time splitting method...');
|
||||
}
|
||||
|
||||
// What is a sample is defined by the analyser, it can be an enrolment, a course, a user, a question
|
||||
@@ -295,7 +367,7 @@ abstract class base {
|
||||
list($sampleids, $samplesdata) = $this->get_all_samples($analysable);
|
||||
|
||||
if (count($sampleids) === 0) {
|
||||
$result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR;
|
||||
$result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD;
|
||||
$result->message = get_string('nodata', 'analytics');
|
||||
return $result;
|
||||
}
|
||||
@@ -312,7 +384,7 @@ abstract class base {
|
||||
if ($this->options['evaluation'] === false) {
|
||||
|
||||
if (empty($ranges)) {
|
||||
$result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR;
|
||||
$result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD;
|
||||
$result->message = get_string('nonewdata', 'analytics');
|
||||
return $result;
|
||||
}
|
||||
@@ -321,7 +393,7 @@ abstract class base {
|
||||
$sampleids = $this->filter_out_train_samples($sampleids, $timesplitting);
|
||||
|
||||
if (count($sampleids) === 0) {
|
||||
$result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR;
|
||||
$result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD;
|
||||
$result->message = get_string('nonewdata', 'analytics');
|
||||
return $result;
|
||||
}
|
||||
@@ -335,7 +407,7 @@ abstract class base {
|
||||
}
|
||||
|
||||
if (count($ranges) === 0) {
|
||||
$result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR;
|
||||
$result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD;
|
||||
$result->message = get_string('nonewtimeranges', 'analytics');
|
||||
return $result;
|
||||
}
|
||||
@@ -375,13 +447,12 @@ abstract class base {
|
||||
$target->add_sample_data($samplesdata);
|
||||
}
|
||||
|
||||
|
||||
// Here we start the memory intensive process that will last until $data var is
|
||||
// unset (until the method is finished basically).
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges, $target);
|
||||
|
||||
if (!$data) {
|
||||
$result->status = \core_analytics\model::ANALYSE_REJECTED_RANGE_PROCESSOR;
|
||||
$result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD;
|
||||
$result->message = get_string('novaliddata', 'analytics');
|
||||
$dataset->close_process();
|
||||
return $result;
|
||||
@@ -410,6 +481,12 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ranges of a time splitting that can be used to predict.
|
||||
*
|
||||
* @param \core_analytics\local\time_splitting\base $timesplitting
|
||||
* @return array
|
||||
*/
|
||||
protected function get_prediction_ranges($timesplitting) {
|
||||
|
||||
$now = time();
|
||||
@@ -426,6 +503,13 @@ abstract class base {
|
||||
return $predictionranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out samples that have already been used for training.
|
||||
*
|
||||
* @param int[] $sampleids
|
||||
* @param \core_analytics\local\time_splitting\base $timesplitting
|
||||
* @return int[]
|
||||
*/
|
||||
protected function filter_out_train_samples($sampleids, $timesplitting) {
|
||||
global $DB;
|
||||
|
||||
@@ -448,6 +532,13 @@ abstract class base {
|
||||
return $sampleids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out samples that have already been used for prediction.
|
||||
*
|
||||
* @param array $ranges
|
||||
* @param \core_analytics\local\time_splitting\base $timesplitting
|
||||
* @return int[]
|
||||
*/
|
||||
protected function filter_out_prediction_ranges($ranges, $timesplitting) {
|
||||
global $DB;
|
||||
|
||||
@@ -465,6 +556,14 @@ abstract class base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves samples that have just been used for training.
|
||||
*
|
||||
* @param int[] $sampleids
|
||||
* @param \core_analytics\local\time_splitting\base $timesplitting
|
||||
* @param \stored_file $file
|
||||
* @return bool
|
||||
*/
|
||||
protected function save_train_samples($sampleids, $timesplitting, $file) {
|
||||
global $DB;
|
||||
|
||||
@@ -481,6 +580,13 @@ abstract class base {
|
||||
return $DB->insert_record('analytics_train_samples', $trainingsamples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves samples that have just been used for prediction.
|
||||
*
|
||||
* @param array $ranges
|
||||
* @param \core_analytics\local\time_splitting\base $timesplitting
|
||||
* @return void
|
||||
*/
|
||||
protected function save_prediction_ranges($ranges, $timesplitting) {
|
||||
global $DB;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Abstract analyser in course basis.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -26,6 +27,7 @@ namespace core_analytics\local\analyser;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Abstract analyser in course basis.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -33,6 +35,11 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class by_course extends base {
|
||||
|
||||
/**
|
||||
* Return the list of courses to analyse.
|
||||
*
|
||||
* @return \core_analytics\course[]
|
||||
*/
|
||||
public function get_courses() {
|
||||
|
||||
// Default to all system courses.
|
||||
@@ -58,6 +65,12 @@ abstract class by_course extends base {
|
||||
return $analysables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the analysed data
|
||||
*
|
||||
* @param bool $includetarget
|
||||
* @return \stored_file[]
|
||||
*/
|
||||
public function get_analysable_data($includetarget) {
|
||||
|
||||
$status = array();
|
||||
@@ -82,6 +95,13 @@ abstract class by_course extends base {
|
||||
return $timesplittingfiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges analysable dataset files into 1.
|
||||
*
|
||||
* @param array $filesbytimesplitting
|
||||
* @param bool $includetarget
|
||||
* @return \stored_file[]
|
||||
*/
|
||||
protected function merge_analysable_files($filesbytimesplitting, $includetarget) {
|
||||
|
||||
$timesplittingfiles = array();
|
||||
|
||||
@@ -35,12 +35,17 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class courses extends by_course {
|
||||
|
||||
/**
|
||||
* Samples origin is course table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_samples_origin() {
|
||||
return 'course';
|
||||
}
|
||||
|
||||
/**
|
||||
* get_sample_analysable
|
||||
* Returns the analysable of a sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @return \core_analytics\analysable
|
||||
@@ -49,16 +54,27 @@ class courses extends by_course {
|
||||
return \core_analytics\course::instance($sampleid);
|
||||
}
|
||||
|
||||
/**
|
||||
* This provides samples' course and context.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function provided_sample_data() {
|
||||
return array('course', 'context');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context of a sample.
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @return \context
|
||||
*/
|
||||
public function sample_access_context($sampleid) {
|
||||
return \context_course::instance($sampleid);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_all_samples
|
||||
* This will return just one course as we analyse 'by_course'.
|
||||
*
|
||||
* @param \core_analytics\analysable $course
|
||||
* @return array
|
||||
@@ -76,7 +92,7 @@ class courses extends by_course {
|
||||
}
|
||||
|
||||
/**
|
||||
* get_samples
|
||||
* Returns samples data from sample ids.
|
||||
*
|
||||
* @param int[] $sampleids
|
||||
* @return array
|
||||
@@ -99,12 +115,12 @@ class courses extends by_course {
|
||||
}
|
||||
|
||||
/**
|
||||
* sample_description
|
||||
* Returns the sample description
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param int $contextid
|
||||
* @param array $sampledata
|
||||
* @return array
|
||||
* @return array array(string, \renderable)
|
||||
*/
|
||||
public function sample_description($sampleid, $contextid, $sampledata) {
|
||||
$description = format_string($sampledata['course']->fullname, true, array('context' => $sampledata['context']));
|
||||
|
||||
@@ -35,12 +35,17 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class site_courses extends sitewide {
|
||||
|
||||
/**
|
||||
* Samples origin is course table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_samples_origin() {
|
||||
return 'course';
|
||||
}
|
||||
|
||||
/**
|
||||
* get_sample_analysable
|
||||
* Returns the sample analysable
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @return \core_analytics\analysable
|
||||
@@ -49,14 +54,31 @@ class site_courses extends sitewide {
|
||||
return new \core_analytics\site();
|
||||
}
|
||||
|
||||
/**
|
||||
* Data this analyer samples provide.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function provided_sample_data() {
|
||||
return array('course', 'context');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sample context.
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @return \context
|
||||
*/
|
||||
public function sample_access_context($sampleid) {
|
||||
return \context_system::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all site courses.
|
||||
*
|
||||
* @param \core_analytics\analysable $site
|
||||
* @return array
|
||||
*/
|
||||
protected function get_all_samples(\core_analytics\analysable $site) {
|
||||
global $DB;
|
||||
|
||||
@@ -77,6 +99,12 @@ class site_courses extends sitewide {
|
||||
return array($sampleids, $courses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all complete samples data from sample ids.
|
||||
*
|
||||
* @param int[] $sampleids
|
||||
* @return array
|
||||
*/
|
||||
public function get_samples($sampleids) {
|
||||
global $DB;
|
||||
|
||||
@@ -95,12 +123,12 @@ class site_courses extends sitewide {
|
||||
}
|
||||
|
||||
/**
|
||||
* sample_description
|
||||
* Returns the description of a sample.
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param int $contextid
|
||||
* @param array $sampledata
|
||||
* @return void
|
||||
* @return array array(string, \renderable)
|
||||
*/
|
||||
public function sample_description($sampleid, $contextid, $sampledata) {
|
||||
$description = format_string($sampledata['course']->fullname, true, array('context' => $sampledata['context']));
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Site-level contents abstract analysable.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -26,6 +27,7 @@ namespace core_analytics\local\analyser;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Site-level contents abstract analysable.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -33,14 +35,16 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class sitewide extends base {
|
||||
|
||||
protected function get_site() {
|
||||
return new \core_analytics\site();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the analysable data.
|
||||
*
|
||||
* @param bool $includetarget
|
||||
* @return \stored_file[] One file for each time splitting method.
|
||||
*/
|
||||
public function get_analysable_data($includetarget) {
|
||||
|
||||
// Here there is a single analysable and it is the system.
|
||||
$analysable = $this->get_site();
|
||||
$analysable = new \core_analytics\site();
|
||||
|
||||
$return = array();
|
||||
|
||||
@@ -54,7 +58,7 @@ abstract class sitewide extends base {
|
||||
\core_analytics\dataset_manager::delete_previous_evaluation_file($this->modelid, $timesplittingid);
|
||||
}
|
||||
|
||||
// We use merge but it is just a copy
|
||||
// We use merge but it is just a copy.
|
||||
$files[$timesplittingid] = \core_analytics\dataset_manager::merge_datasets(array($file), $this->modelid,
|
||||
$timesplittingid, $this->options['evaluation'], $includetarget);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Student enrolments analyser.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -28,6 +29,9 @@ defined('MOODLE_INTERNAL') || die();
|
||||
require_once($CFG->dirroot . '/lib/enrollib.php');
|
||||
|
||||
/**
|
||||
* Student enrolments analyser.
|
||||
*
|
||||
* It does return all student enrolments including the suspended ones.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -40,28 +44,52 @@ class student_enrolments extends by_course {
|
||||
*/
|
||||
protected $samplecourses = array();
|
||||
|
||||
/**
|
||||
* Defines the origin of the samples in the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_samples_origin() {
|
||||
return 'user_enrolments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the student enrolment course context.
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @return \context
|
||||
*/
|
||||
public function sample_access_context($sampleid) {
|
||||
return \context_course::instance($this->get_sample_courseid($sampleid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the student enrolment course.
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @return \core_analytics\analysable
|
||||
*/
|
||||
public function get_sample_analysable($sampleid) {
|
||||
$course = enrol_get_course_by_user_enrolment_id($sampleid);
|
||||
return \core_analytics\course::instance($course);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provided by get_all_samples & get_samples.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function provided_sample_data() {
|
||||
return array('user_enrolments', 'context', 'course', 'user');
|
||||
}
|
||||
|
||||
/**
|
||||
* All course enrolments.
|
||||
* All course student enrolments.
|
||||
*
|
||||
* It does return all student enrolments including the suspended ones.
|
||||
*
|
||||
* @param \core_analytics\analysable $course
|
||||
* @return void
|
||||
* @return array
|
||||
*/
|
||||
protected function get_all_samples(\core_analytics\analysable $course) {
|
||||
|
||||
@@ -111,6 +139,12 @@ class student_enrolments extends by_course {
|
||||
return array(array_combine($enrolids, $enrolids), $samplesdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all samples from the samples ids.
|
||||
*
|
||||
* @param int[] $sampleids
|
||||
* @return array
|
||||
*/
|
||||
public function get_samples($sampleids) {
|
||||
global $DB;
|
||||
|
||||
@@ -189,7 +223,7 @@ class student_enrolments extends by_course {
|
||||
* @param int $sampleid
|
||||
* @param int $contextid
|
||||
* @param array $sampledata
|
||||
* @return array
|
||||
* @return array array(string, \renderable)
|
||||
*/
|
||||
public function sample_description($sampleid, $contextid, $sampledata) {
|
||||
$description = fullname($sampledata['user'], true, array('context' => $contextid));
|
||||
|
||||
@@ -35,14 +35,33 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class any_access_after_end extends binary {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:accessesafterend', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
return array('user', 'course', 'context');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $samplesorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $samplesorigin, $starttime = false, $endtime = false) {
|
||||
global $DB;
|
||||
|
||||
|
||||
@@ -35,14 +35,33 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class any_access_before_start extends binary {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:accessesbeforestart', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
return array('user', 'course', 'context');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $samplesorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $samplesorigin, $starttime = false, $endtime = false) {
|
||||
global $DB;
|
||||
|
||||
|
||||
@@ -35,15 +35,34 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class any_write_action extends binary {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:anywrite', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
// User is not required, calculate_sample can handle its absence.
|
||||
return array('context');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $sampleorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $sampleorigin, $starttime = false, $endtime = false) {
|
||||
global $DB;
|
||||
|
||||
|
||||
@@ -35,12 +35,18 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class base extends \core_analytics\calculable {
|
||||
|
||||
/**
|
||||
* Min value an indicator can return.
|
||||
*/
|
||||
const MIN_VALUE = -1;
|
||||
|
||||
/**
|
||||
* Max value an indicator can return.
|
||||
*/
|
||||
const MAX_VALUE = 1;
|
||||
|
||||
/**
|
||||
* Converts the calculated indicators to feature/s.
|
||||
* Converts the calculated indicators to dataset feature/s.
|
||||
*
|
||||
* @param float|int[] $calculatedvalues
|
||||
* @return array
|
||||
@@ -60,26 +66,57 @@ abstract class base extends \core_analytics\calculable {
|
||||
*/
|
||||
abstract protected function calculate_sample($sampleid, $sampleorigin, $starttime, $endtime);
|
||||
|
||||
/**
|
||||
* Should this value be displayed?
|
||||
*
|
||||
* Indicators providing multiple features can be used this method to discard some of them.
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $subtype
|
||||
* @return bool
|
||||
*/
|
||||
public function should_be_displayed($value, $subtype) {
|
||||
// We should everything by default.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
* Allows indicators to specify data they need.
|
||||
*
|
||||
* e.g. A model using courses as samples will not provide users data, but an indicator like
|
||||
* "user is hungry" needs user data.
|
||||
*
|
||||
* @return null|string[] Name of the required elements (use the database tablename)
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the indicator.
|
||||
*
|
||||
* Useful to reset cached data.
|
||||
*
|
||||
* @return \core_analytics\local\indicator\base
|
||||
*/
|
||||
public static function instance() {
|
||||
return new static();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum value an indicator calculation can return.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function get_max_value() {
|
||||
return self::MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum value an indicator calculation can return.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function get_min_value() {
|
||||
return self::MIN_VALUE;
|
||||
}
|
||||
@@ -89,7 +126,7 @@ abstract class base extends \core_analytics\calculable {
|
||||
*
|
||||
* Returns an array of values which size matches $sampleids size.
|
||||
*
|
||||
* @param array $sampleids
|
||||
* @param int[] $sampleids
|
||||
* @param string $samplesorigin
|
||||
* @param integer $starttime Limit the calculation to this timestart
|
||||
* @param integer $endtime Limit the calculation to this timeend
|
||||
@@ -118,17 +155,4 @@ abstract class base extends \core_analytics\calculable {
|
||||
|
||||
return $calculations;
|
||||
}
|
||||
|
||||
protected static function add_samples_averages() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function get_average_columns() {
|
||||
return array('mean');
|
||||
}
|
||||
|
||||
protected function calculate_averages($values) {
|
||||
$mean = array_sum($values) / count($values);
|
||||
return array($mean);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class binary extends discrete {
|
||||
|
||||
/**
|
||||
* get_classes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static final function get_classes() {
|
||||
// It does not really matter, all \core_analytics\local\indicator\discrete get_classes calls have been overwriten as we
|
||||
// only need 1 column here.
|
||||
@@ -79,11 +84,22 @@ abstract class binary extends discrete {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get_feature_headers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_feature_headers() {
|
||||
// Just 1 single feature obtained from the calculated value.
|
||||
return array(get_called_class());
|
||||
}
|
||||
|
||||
/**
|
||||
* to_features
|
||||
*
|
||||
* @param array $calculatedvalues
|
||||
* @return array
|
||||
*/
|
||||
protected function to_features($calculatedvalues) {
|
||||
// Indicators with binary values have only 1 feature for indicator, here we do nothing else
|
||||
// than converting each sample scalar value to an array of scalars with 1 element.
|
||||
|
||||
@@ -35,10 +35,12 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class community_of_inquiry_activity extends linear {
|
||||
|
||||
protected $course = null;
|
||||
/**
|
||||
* TODO This should ideally be reused by cognitive depth and social breadth.
|
||||
*
|
||||
* @var \core_analytics\course
|
||||
*/
|
||||
protected $course = null;
|
||||
|
||||
/**
|
||||
* @var array Array of logs by [contextid][userid]
|
||||
*/
|
||||
protected $activitylogs = null;
|
||||
@@ -49,12 +51,12 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
protected $grades = null;
|
||||
|
||||
/**
|
||||
* @const Constant cognitive indicator type.
|
||||
* Constant cognitive indicator type.
|
||||
*/
|
||||
const INDICATOR_COGNITIVE = "cognitve";
|
||||
|
||||
/**
|
||||
* @const Constant social indicator type.
|
||||
* Constant social indicator type.
|
||||
*/
|
||||
const INDICATOR_SOCIAL = "social";
|
||||
|
||||
@@ -63,7 +65,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
*
|
||||
* @var string The activity name (e.g. assign or quiz)
|
||||
*/
|
||||
final protected function get_activity_type() {
|
||||
protected final function get_activity_type() {
|
||||
$class = get_class($this);
|
||||
$package = stristr($class, "\\", true);
|
||||
$type = str_replace("mod_", "", $package);
|
||||
@@ -73,21 +75,47 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the potential level of cognitive depth.
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_cognitive_depth_level(\cm_info $cm) {
|
||||
throw new \coding_exception('Overwrite get_cognitive_depth_level method to set your activity potential cognitive ' .
|
||||
'depth level');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the potential level of social breadth.
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
throw new \coding_exception('Overwrite get_social_breadth_level method to set your activity potential social ' .
|
||||
'breadth level');
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
// Only course because the indicator is valid even without students.
|
||||
return array('course');
|
||||
}
|
||||
|
||||
/**
|
||||
* Do activity logs contain any log of user in this context?
|
||||
*
|
||||
* If user is empty we look for any log in this context.
|
||||
*
|
||||
* @param int $contextid
|
||||
* @param \stdClass|false $user
|
||||
* @return bool
|
||||
*/
|
||||
protected final function any_log($contextid, $user) {
|
||||
if (empty($this->activitylogs[$contextid])) {
|
||||
return false;
|
||||
@@ -103,6 +131,15 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do activity logs contain any write log of user in this context?
|
||||
*
|
||||
* If user is empty we look for any write log in this context.
|
||||
*
|
||||
* @param int $contextid
|
||||
* @param \stdClass|false $user
|
||||
* @return bool
|
||||
*/
|
||||
protected final function any_write_log($contextid, $user) {
|
||||
if (empty($this->activitylogs[$contextid])) {
|
||||
return false;
|
||||
@@ -127,6 +164,17 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there any feedback activity log for this user in this context?
|
||||
*
|
||||
* This method returns true if $user is empty and there is any feedback activity logs.
|
||||
*
|
||||
* @param string $action
|
||||
* @param \cm_info $cm
|
||||
* @param int $contextid
|
||||
* @param \stdClass|false $user
|
||||
* @return bool
|
||||
*/
|
||||
protected function any_feedback($action, \cm_info $cm, $contextid, $user) {
|
||||
|
||||
if (!in_array($action, 'submitted', 'replied', 'viewed')) {
|
||||
@@ -175,29 +223,76 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
return $this->feedback_post_action($cm, $contextid, $userid, $this->feedback_viewed_events(), $after);
|
||||
}
|
||||
|
||||
/**
|
||||
* $cm is used for this method overrides.
|
||||
*
|
||||
* This function must be fast.
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @param mixed $contextid
|
||||
* @param mixed $userid
|
||||
* @param int $after Timestamp, defaults to the graded date or false if we don't check the date.
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_replied(\cm_info $cm, $contextid, $userid, $after = null) {
|
||||
return $this->feedback_post_action($cm, $contextid, $userid, $this->feedback_replied_events(), $after);
|
||||
}
|
||||
|
||||
/**
|
||||
* $cm is used for this method overrides.
|
||||
*
|
||||
* This function must be fast.
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @param mixed $contextid
|
||||
* @param mixed $userid
|
||||
* @param int $after Timestamp, defaults to the graded date or false if we don't check the date.
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_submitted(\cm_info $cm, $contextid, $userid, $after = null) {
|
||||
return $this->feedback_post_action($cm, $contextid, $userid, $this->feedback_submitted_events(), $after);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of events that involve viewing feedback from other users.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_viewed_events() {
|
||||
throw new \coding_exception('Activities with a potential cognitive or social level that include viewing feedback ' .
|
||||
'should define "feedback_viewed_events" method or should override feedback_viewed method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of events that involve replying to feedback from other users.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_replied_events() {
|
||||
throw new \coding_exception('Activities with a potential cognitive or social level that include replying to feedback ' .
|
||||
'should define "feedback_replied_events" method or should override feedback_replied method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of events that involve submitting something after receiving feedback from other users.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_submitted_events() {
|
||||
throw new \coding_exception('Activities with a potential cognitive or social level that include viewing feedback ' .
|
||||
'should define "feedback_submitted_events" method or should override feedback_submitted method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this user in this context did any of the provided actions (events)
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @param int $contextid
|
||||
* @param int $userid
|
||||
* @param string[] $eventnames
|
||||
* @param int|false $after
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $eventnames, $after = null) {
|
||||
if ($after === null) {
|
||||
if ($this->feedback_check_grades()) {
|
||||
@@ -237,7 +332,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
}
|
||||
|
||||
/**
|
||||
* get_graded_date
|
||||
* Returns the date a user was graded.
|
||||
*
|
||||
* @param int $contextid
|
||||
* @param int $userid
|
||||
@@ -268,6 +363,15 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
return $after;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the activities the user had access to between a time period.
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $tablename
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return array
|
||||
*/
|
||||
protected function get_student_activities($sampleid, $tablename, $starttime, $endtime) {
|
||||
|
||||
// May not be available.
|
||||
@@ -307,6 +411,14 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
return $useractivities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch acitivity logs from database
|
||||
*
|
||||
* @param array $activities
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return array
|
||||
*/
|
||||
protected function fetch_activity_logs($activities, $starttime = false, $endtime = false) {
|
||||
global $DB;
|
||||
|
||||
@@ -330,7 +442,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
$processedevents[$event->contextid][$event->userid] = array();
|
||||
}
|
||||
|
||||
// contextid and userid have already been used to index the events, the next field to index by is eventname:
|
||||
// Contextid and userid have already been used to index the events, the next field to index by is eventname:
|
||||
// crud is unique per eventname, courseid is the same for all records and we append timecreated.
|
||||
if (!isset($processedevents[$event->contextid][$event->userid][$event->eventname])) {
|
||||
|
||||
@@ -358,7 +470,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
/**
|
||||
* Whether grades should be checked or not when looking for feedback.
|
||||
*
|
||||
* @return void
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_check_grades() {
|
||||
return true;
|
||||
@@ -367,10 +479,10 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
/**
|
||||
* cognitive_calculate_sample
|
||||
*
|
||||
* @param $sampleid
|
||||
* @param $tablename
|
||||
* @param bool $starttime
|
||||
* @param bool $endtime
|
||||
* @param int $sampleid
|
||||
* @param string $tablename
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float|int|null
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
@@ -404,7 +516,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
$score += $scoreperlevel * 5;
|
||||
break;
|
||||
}
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
|
||||
case 4:
|
||||
// Cognitive level 4 is to comment on feedback.
|
||||
@@ -412,7 +524,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
$score += $scoreperlevel * 4;
|
||||
break;
|
||||
}
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
|
||||
case 3:
|
||||
// Cognitive level 3 is to view feedback.
|
||||
@@ -422,7 +534,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
$score += $scoreperlevel * 3;
|
||||
break;
|
||||
}
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
|
||||
case 2:
|
||||
// Cognitive depth level 2 is to submit content.
|
||||
@@ -431,7 +543,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
$score += $scoreperlevel * 2;
|
||||
break;
|
||||
}
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 1.
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 1.
|
||||
|
||||
case 1:
|
||||
// Cognitive depth level 1 is just accessing the activity.
|
||||
@@ -456,10 +568,10 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
/**
|
||||
* social_calculate_sample
|
||||
*
|
||||
* @param $sampleid
|
||||
* @param $tablename
|
||||
* @param bool $starttime
|
||||
* @param bool $endtime
|
||||
* @param int $sampleid
|
||||
* @param string $tablename
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float|int|null
|
||||
*/
|
||||
protected function social_calculate_sample($sampleid, $tablename, $starttime = false, $endtime = false) {
|
||||
@@ -486,14 +598,15 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
// TODO Add support for other levels than 2.
|
||||
switch ($potentiallevel) {
|
||||
case 2:
|
||||
// Social breadth level 2 is to view feedback. (Same as cognitive level 3)
|
||||
// Social breadth level 2 is to view feedback. (Same as cognitive level 3).
|
||||
|
||||
if ($this->any_feedback('viewed', $cm, $contextid, $user)) {
|
||||
// Max score for level 2.
|
||||
$score += $scoreperlevel * 2;
|
||||
break;
|
||||
}
|
||||
// The user didn't reach the activity max social breadth, continue with level 1.
|
||||
// The user didn't reach the activity max social breadth, continue with level 1.
|
||||
|
||||
case 1:
|
||||
// Social breadth level 1 is just accessing the activity.
|
||||
if ($this->any_log($contextid, $user)) {
|
||||
@@ -515,12 +628,12 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @throws \coding_exception
|
||||
* @param int $sampleid
|
||||
* @param string $tablename
|
||||
* @param bool $starttime
|
||||
* @param bool $endtime
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float|int|null
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $tablename, $starttime = false, $endtime = false) {
|
||||
if ($this->get_indicator_type() == self::INDICATOR_COGNITIVE) {
|
||||
@@ -534,7 +647,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
/**
|
||||
* Defines indicator type.
|
||||
*
|
||||
* @return mixed
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_indicator_type();
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ abstract class discrete extends base {
|
||||
throw new \coding_exception('Please overwrite get_classes() specifying your discrete-values\' indicator classes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 feature header for each of the classes.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function get_feature_headers() {
|
||||
$fullclassname = get_called_class();
|
||||
|
||||
@@ -55,6 +60,13 @@ abstract class discrete extends base {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the value should be displayed or not.
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $subtype
|
||||
* @return bool
|
||||
*/
|
||||
public function should_be_displayed($value, $subtype) {
|
||||
if ($value != static::get_max_value()) {
|
||||
// Discrete values indicators are converted internally to 1 feature per indicator, we are only interested
|
||||
@@ -94,6 +106,14 @@ abstract class discrete extends base {
|
||||
return \core_analytics\calculable::OUTCOME_NEUTRAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* From calculated values to dataset features.
|
||||
*
|
||||
* One column for each class.
|
||||
*
|
||||
* @param float[] $calculatedvalues
|
||||
* @return float[]
|
||||
*/
|
||||
protected function to_features($calculatedvalues) {
|
||||
|
||||
$classes = self::get_classes();
|
||||
|
||||
@@ -44,6 +44,11 @@ abstract class linear extends base {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_feature_headers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_feature_headers() {
|
||||
|
||||
$fullclassname = get_called_class();
|
||||
@@ -57,6 +62,13 @@ abstract class linear extends base {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* should_be_displayed
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $subtype
|
||||
* @return bool
|
||||
*/
|
||||
public function should_be_displayed($value, $subtype) {
|
||||
if ($subtype != false) {
|
||||
return false;
|
||||
@@ -91,6 +103,12 @@ abstract class linear extends base {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the calculated values to a list of features for the dataset.
|
||||
*
|
||||
* @param array $calculatedvalues
|
||||
* @return array
|
||||
*/
|
||||
protected function to_features($calculatedvalues) {
|
||||
|
||||
// Null mean if all calculated values are null.
|
||||
|
||||
@@ -35,15 +35,34 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class read_actions extends linear {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:readactions', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
// User is not required, calculate_sample can handle its absence.
|
||||
return array('context');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $sampleorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $sampleorigin, $starttime = false, $endtime = false) {
|
||||
|
||||
$select = '';
|
||||
@@ -67,7 +86,7 @@ class read_actions extends linear {
|
||||
// # Done absolutely nothing
|
||||
// # Not much really, just accessing the course once a week
|
||||
// # More than just accessing the course, some interaction
|
||||
// # Significant contribution, will depend on the course anyway
|
||||
// # Significant contribution, will depend on the course anyway.
|
||||
|
||||
// We need to adapt the limits to the time range duration.
|
||||
$nweeks = $this->get_time_range_weeks_number($starttime, $endtime);
|
||||
|
||||
@@ -35,15 +35,33 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class user_profile_set extends linear {
|
||||
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:completeduserprofile', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
return array('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $sampleorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $sampleorigin, $starttime = false, $endtime = false) {
|
||||
global $CFG;
|
||||
|
||||
|
||||
@@ -35,14 +35,33 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class user_track_forums extends binary {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:userforumstracking', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
return array('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $samplesorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $samplesorigin, $starttime = false, $endtime = false) {
|
||||
$user = $this->retrieve('user', $sampleid);
|
||||
return ($user->trackforums) ? self::get_max_value() : self::get_min_value();
|
||||
|
||||
@@ -186,7 +186,6 @@ abstract class base extends \core_analytics\calculable {
|
||||
$message->smallmessage = get_string('insightinfomessage', 'analytics', $insighturl->out());
|
||||
$message->contexturl = $insighturl->out(false);
|
||||
|
||||
|
||||
message_send($message);
|
||||
}
|
||||
}
|
||||
@@ -240,7 +239,7 @@ abstract class base extends \core_analytics\calculable {
|
||||
* Should the model callback be triggered?
|
||||
*
|
||||
* @param mixed $predictedvalue
|
||||
* @param float $predictedscore
|
||||
* @param float $predictionscore
|
||||
* @return bool
|
||||
*/
|
||||
public function triggers_callback($predictedvalue, $predictionscore) {
|
||||
@@ -292,7 +291,8 @@ abstract class base extends \core_analytics\calculable {
|
||||
$calculatedvalue = $this->calculate_sample($sampleid, $analysable, $starttime, $endtime);
|
||||
|
||||
if (!is_null($calculatedvalue)) {
|
||||
if ($this->is_linear() && ($calculatedvalue > static::get_max_value() || $calculatedvalue < static::get_min_value())) {
|
||||
if ($this->is_linear() &&
|
||||
($calculatedvalue > static::get_max_value() || $calculatedvalue < static::get_min_value())) {
|
||||
throw new \coding_exception('Calculated values should be higher than ' . static::get_min_value() .
|
||||
' and lower than ' . static::get_max_value() . '. ' . $calculatedvalue . ' received');
|
||||
} else if (!$this->is_linear() && static::is_a_class($calculatedvalue) === false) {
|
||||
@@ -310,6 +310,7 @@ abstract class base extends \core_analytics\calculable {
|
||||
*
|
||||
* @param int[] $sampleids
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param bool $fortraining
|
||||
* @return void
|
||||
*/
|
||||
public function filter_out_invalid_samples(&$sampleids, \core_analytics\analysable $analysable, $fortraining = true) {
|
||||
|
||||
@@ -35,9 +35,12 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class binary extends discrete {
|
||||
|
||||
/**
|
||||
* is_linear
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_linear() {
|
||||
// TODO Remove this discrete override once prediction processors support
|
||||
// multiclass classifiers; this method will be moved to discrete.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -62,6 +65,13 @@ abstract class binary extends discrete {
|
||||
return array(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the calculated value a positive outcome of this target?
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $ignoredsubtype
|
||||
* @return int
|
||||
*/
|
||||
public function get_calculation_outcome($value, $ignoredsubtype = false) {
|
||||
|
||||
if (!self::is_a_class($value)) {
|
||||
@@ -81,6 +91,11 @@ abstract class binary extends discrete {
|
||||
return self::OUTCOME_VERY_POSITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* classes_description
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected static function classes_description() {
|
||||
return array(
|
||||
get_string('yes'),
|
||||
|
||||
@@ -35,11 +35,22 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class discrete extends base {
|
||||
|
||||
/**
|
||||
* Are this target calculations linear values?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_linear() {
|
||||
// Not supported yet.
|
||||
throw new \coding_exception('Sorry, this version\'s prediction processors only support targets with binary values.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided class one of this target valid classes?
|
||||
*
|
||||
* @param string $class
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_a_class($class) {
|
||||
return (in_array($class, static::get_classes()));
|
||||
}
|
||||
@@ -48,7 +59,7 @@ abstract class discrete extends base {
|
||||
* get_display_value
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $subtype
|
||||
* @param string $ignoredsubtype
|
||||
* @return string
|
||||
*/
|
||||
public function get_display_value($value, $ignoredsubtype = false) {
|
||||
@@ -57,17 +68,19 @@ abstract class discrete extends base {
|
||||
throw new \moodle_exception('errorpredictionformat', 'analytics');
|
||||
}
|
||||
|
||||
// array_values to discard any possible weird keys devs used.
|
||||
// To discard any possible weird keys devs used.
|
||||
$classes = array_values(static::get_classes());
|
||||
$descriptions = array_values(static::classes_description());
|
||||
|
||||
if (count($classes) !== count($descriptions)) {
|
||||
throw new \coding_exception('You need to describe all your classes (' . json_encode($classes) . ') in self::classes_description');
|
||||
throw new \coding_exception('You need to describe all your classes (' . json_encode($classes) .
|
||||
') in self::classes_description');
|
||||
}
|
||||
|
||||
$key = array_search($value, $classes);
|
||||
if ($key === false) {
|
||||
throw new \coding_exception('You need to describe all your classes (' . json_encode($classes) . ') in self::classes_description');
|
||||
throw new \coding_exception('You need to describe all your classes (' . json_encode($classes) .
|
||||
') in self::classes_description');
|
||||
}
|
||||
|
||||
return $descriptions[$key];
|
||||
@@ -91,8 +104,8 @@ abstract class discrete extends base {
|
||||
return self::OUTCOME_OK;
|
||||
}
|
||||
|
||||
debugging('Please overwrite \core_analytics\local\target\discrete::get_calculation_outcome, all your target classes are styled ' .
|
||||
'the same way otherwise', DEBUG_DEVELOPER);
|
||||
debugging('Please overwrite \core_analytics\local\target\discrete::get_calculation_outcome, all your target ' .
|
||||
'classes are styled the same way otherwise', DEBUG_DEVELOPER);
|
||||
return self::OUTCOME_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,23 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class linear extends base {
|
||||
|
||||
/**
|
||||
* Are the calculated values this target returns linear values?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_linear() {
|
||||
// Not supported yet.
|
||||
throw new \coding_exception('Sorry, this version\'s prediction processors only support targets with binary values.');
|
||||
}
|
||||
|
||||
/**
|
||||
* How positive is this calculated value?
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $ignoredsubtype
|
||||
* @return int
|
||||
*/
|
||||
public function get_calculated_outcome($value, $ignoredsubtype = false) {
|
||||
|
||||
// This is very generic, targets will probably be interested in overwriting this.
|
||||
|
||||
@@ -26,6 +26,13 @@ namespace core_analytics\local\time_splitting;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Base time splitting method.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class base {
|
||||
|
||||
/**
|
||||
@@ -59,8 +66,22 @@ abstract class base {
|
||||
*/
|
||||
protected static $indicators = [];
|
||||
|
||||
/**
|
||||
* Define the time splitting methods ranges.
|
||||
*
|
||||
* 'time' value defines when predictions are executed, their values will be compared with
|
||||
* the current time in ready_to_predict
|
||||
*
|
||||
* @return array('start' => time(), 'end' => time(), 'time' => time())
|
||||
*/
|
||||
abstract protected function define_ranges();
|
||||
|
||||
/**
|
||||
* The time splitting method name.
|
||||
*
|
||||
* It is very recommendable to overwrite this method as this name appears in the UI.
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->get_id();
|
||||
}
|
||||
@@ -86,6 +107,11 @@ abstract class base {
|
||||
$this->validate_ranges();
|
||||
}
|
||||
|
||||
/**
|
||||
* get_analysable
|
||||
*
|
||||
* @return \core_analytics\analysable
|
||||
*/
|
||||
public function get_analysable() {
|
||||
return $this->analysable;
|
||||
}
|
||||
@@ -93,12 +119,19 @@ abstract class base {
|
||||
/**
|
||||
* Returns whether the course can be processed by this time splitting method or not.
|
||||
*
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_analysable(\core_analytics\analysable $analysable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we predict this time range now?
|
||||
*
|
||||
* @param array $range
|
||||
* @return bool
|
||||
*/
|
||||
public function ready_to_predict($range) {
|
||||
if ($range['time'] <= time()) {
|
||||
return true;
|
||||
@@ -111,7 +144,7 @@ abstract class base {
|
||||
*
|
||||
* @param array $sampleids
|
||||
* @param string $samplesorigin
|
||||
* @param \core_analytics\local\indicator\base $indicators
|
||||
* @param \core_analytics\local\indicator\base[] $indicators
|
||||
* @param array $ranges
|
||||
* @param \core_analytics\local\target\base $target
|
||||
* @return array
|
||||
@@ -155,6 +188,10 @@ abstract class base {
|
||||
/**
|
||||
* Calculates indicators.
|
||||
*
|
||||
* @param array $sampleids
|
||||
* @param string $samplesorigin
|
||||
* @param \core_analytics\local\indicator\base[] $indicators
|
||||
* @param array $ranges
|
||||
* @return array
|
||||
*/
|
||||
protected function calculate_indicators($sampleids, $samplesorigin, $indicators, $ranges) {
|
||||
@@ -197,6 +234,8 @@ abstract class base {
|
||||
*
|
||||
* This will identify the sample as belonging to a specific range.
|
||||
*
|
||||
* @param array $dataset
|
||||
* @param array $calculatedtarget
|
||||
* @return void
|
||||
*/
|
||||
protected function fill_dataset(&$dataset, $calculatedtarget = false) {
|
||||
@@ -244,6 +283,9 @@ abstract class base {
|
||||
* .....
|
||||
* ----------------------------------------------------
|
||||
*
|
||||
* @param array $dataset
|
||||
* @param \core_analytics\local\indicator\base[] $indicators
|
||||
* @param \core_analytics\local\target\base|false $target
|
||||
* @return void
|
||||
*/
|
||||
protected function add_metadata(&$dataset, $indicators, $target = false) {
|
||||
@@ -284,6 +326,7 @@ abstract class base {
|
||||
/**
|
||||
* get_range_by_index
|
||||
*
|
||||
* @param int $rangeindex
|
||||
* @return array
|
||||
*/
|
||||
public function get_range_by_index($rangeindex) {
|
||||
@@ -293,14 +336,34 @@ abstract class base {
|
||||
return $this->ranges[$rangeindex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique sample id (sample in a range index).
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param int $rangeindex
|
||||
* @return string
|
||||
*/
|
||||
public function append_rangeindex($sampleid, $rangeindex) {
|
||||
return $sampleid . '-' . $rangeindex;
|
||||
}
|
||||
|
||||
public function infer_sample_info($sampleid) {
|
||||
return explode('-', $sampleid);
|
||||
/**
|
||||
* Returns the sample id and the range index from a uniquesampleid.
|
||||
*
|
||||
* @param string $uniquesampleid
|
||||
* @return array array($sampleid, $rangeindex)
|
||||
*/
|
||||
public function infer_sample_info($uniquesampleid) {
|
||||
return explode('-', $uniquesampleid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers for the csv file based on the indicators and the target.
|
||||
*
|
||||
* @param \core_analytics\local\indicator\base[] $indicators
|
||||
* @param \core_analytics\local\target\base|false $target
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_headers($indicators, $target = false) {
|
||||
// 3rd column will contain the indicator ids.
|
||||
$headers = array();
|
||||
@@ -335,7 +398,7 @@ abstract class base {
|
||||
/**
|
||||
* Validates the time splitting method ranges.
|
||||
*
|
||||
* @throw \coding_exception
|
||||
* @throws \coding_exception
|
||||
* @return void
|
||||
*/
|
||||
protected function validate_ranges() {
|
||||
|
||||
@@ -35,10 +35,20 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class deciles extends base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('timesplitting:deciles', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* define_ranges
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function define_ranges() {
|
||||
$rangeduration = floor(($this->analysable->get_end() - $this->analysable->get_start()) / 10);
|
||||
|
||||
|
||||
@@ -35,10 +35,20 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class deciles_accum extends base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('timesplitting:decilesaccum', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* define_ranges
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function define_ranges() {
|
||||
$rangeduration = floor(($this->analysable->get_end() - $this->analysable->get_start()) / 10);
|
||||
|
||||
|
||||
@@ -28,16 +28,41 @@ namespace core_analytics\local\time_splitting;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* No time splitting method.
|
||||
*
|
||||
* Used when time is not a factor to consider into the equation.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class no_splitting extends base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('timesplitting:nosplitting', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* ready_to_predict
|
||||
*
|
||||
* @param array $range
|
||||
* @return true
|
||||
*/
|
||||
public function ready_to_predict($range) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* define_ranges
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function define_ranges() {
|
||||
return [
|
||||
[
|
||||
|
||||
@@ -35,10 +35,20 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class quarters extends base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('timesplitting:quarters', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* define_ranges
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function define_ranges() {
|
||||
$duration = floor(($this->analysable->get_end() - $this->analysable->get_start()) / 4);
|
||||
return [
|
||||
|
||||
@@ -35,10 +35,20 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class quarters_accum extends base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('timesplitting:quartersaccum', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* define_ranges
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function define_ranges() {
|
||||
$duration = floor(($this->analysable->get_end() - $this->analysable->get_start()) / 4);
|
||||
return [
|
||||
|
||||
@@ -26,14 +26,31 @@ namespace core_analytics\local\time_splitting;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Single time splitting method.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class single_range extends base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('timesplitting:singlerange', 'analytics');
|
||||
}
|
||||
|
||||
/**
|
||||
* One single range covering all analysable duration.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function define_ranges() {
|
||||
// time == 0 because we want it to start predicting from the beginning.
|
||||
// Key 'time' == 0 because we want it to start predicting from the beginning.
|
||||
return [
|
||||
[
|
||||
'start' => $this->analysable->get_start(),
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Weekly time splitting method.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_analytics\local\time_splitting;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
abstract class weekly extends base {
|
||||
|
||||
public function get_name() {
|
||||
return get_string('timesplitting:weekly', 'analytics');
|
||||
}
|
||||
|
||||
public function is_valid_analysable(\core_analytics\analysable $analysable) {
|
||||
$diff = $analysable->get_end() - $analysable->get_start();
|
||||
$nweeks = round($diff / WEEKSECS);
|
||||
if ($nweeks > 520) {
|
||||
// More than 10 years...
|
||||
return get_string('coursetoolong', 'analytics');
|
||||
}
|
||||
return parent::is_valid_analysable($analysable);
|
||||
}
|
||||
|
||||
protected function define_ranges() {
|
||||
|
||||
$ranges = array();
|
||||
|
||||
// It is more important to work with a proper end date than the start date.
|
||||
$i = 0;
|
||||
do {
|
||||
|
||||
$dt = new \DateTime();
|
||||
$dt->setTimestamp($this->analysable->get_end());
|
||||
$dt->modify('-' . $i . ' weeks');
|
||||
$rangeend = $dt->getTimestamp();
|
||||
|
||||
$dt->modify('-1 weeks');
|
||||
$rangestart = $dt->getTimestamp();
|
||||
|
||||
$ranges[] = array(
|
||||
'start' => $rangestart,
|
||||
'end' => $rangeend,
|
||||
'time' => $rangeend
|
||||
);
|
||||
|
||||
$i++;
|
||||
|
||||
} while ($this->analysable->get_start() < $rangestart);
|
||||
|
||||
$ranges = array_reverse($ranges, false);
|
||||
|
||||
// Is not worth trying to predict during the first weeks.
|
||||
array_shift($ranges);
|
||||
array_shift($ranges);
|
||||
|
||||
return $ranges;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Weekly time splitting method.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_analytics\local\time_splitting;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
abstract class weekly_accum extends base {
|
||||
|
||||
public function get_name() {
|
||||
return get_string('timesplitting:weeklyaccum', 'analytics');
|
||||
}
|
||||
|
||||
public function is_valid_analysable(\core_analytics\analysable $analysable) {
|
||||
$diff = $analysable->get_end() - $analysable->get_start();
|
||||
$nweeks = round($diff / WEEKSECS);
|
||||
if ($nweeks > 520) {
|
||||
// More than 10 years...
|
||||
return false;
|
||||
}
|
||||
return parent::is_valid_analysable($analysable);
|
||||
}
|
||||
|
||||
protected function define_ranges() {
|
||||
|
||||
$ranges = array();
|
||||
|
||||
// It is more important to work with a proper end date than start date.
|
||||
$i = 0;
|
||||
do {
|
||||
|
||||
$dt = new \DateTime();
|
||||
$dt->setTimestamp($this->analysable->get_end());
|
||||
$dt->modify('-' . $i . ' weeks');
|
||||
$rangeend = $dt->getTimestamp();
|
||||
|
||||
// Used to calculate when we are done creating new ranges.
|
||||
$dt->modify('-1 weeks');
|
||||
$rangestart = $dt->getTimestamp();
|
||||
|
||||
// Accumulative, always from the course start.
|
||||
$ranges[] = array(
|
||||
'start' => $this->analysable->get_start(),
|
||||
'end' => $rangeend,
|
||||
'time' => $rangeend
|
||||
);
|
||||
|
||||
$i++;
|
||||
|
||||
} while ($this->analysable->get_start() < $rangestart);
|
||||
|
||||
$ranges = array_reverse($ranges, false);
|
||||
|
||||
// Is not worth trying to predict during the first weeks.
|
||||
array_shift($ranges);
|
||||
array_shift($ranges);
|
||||
|
||||
return $ranges;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -335,7 +335,7 @@ class manager {
|
||||
|
||||
self::check_can_list_insights($context);
|
||||
|
||||
$models = \core_analytics\manager::get_all_models(true, true, $context);
|
||||
$models = self::get_all_models(true, true, $context);
|
||||
foreach ($models as $key => $model) {
|
||||
// Check that it not only have predictions but also generates insights from them.
|
||||
if (!$model->uses_insights()) {
|
||||
@@ -366,7 +366,7 @@ class manager {
|
||||
$context = \context::instance_by_id($predictionobj->contextid);
|
||||
}
|
||||
|
||||
\core_analytics\manager::check_can_list_insights($context);
|
||||
self::check_can_list_insights($context);
|
||||
|
||||
$model = new \core_analytics\model($predictionobj->modelid);
|
||||
$sampledata = $model->prediction_sample_data($predictionobj);
|
||||
|
||||
@@ -35,19 +35,59 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class model {
|
||||
|
||||
/**
|
||||
* All as expected.
|
||||
*/
|
||||
const OK = 0;
|
||||
|
||||
/**
|
||||
* There was a problem.
|
||||
*/
|
||||
const GENERAL_ERROR = 1;
|
||||
|
||||
/**
|
||||
* No dataset to analyse.
|
||||
*/
|
||||
const NO_DATASET = 2;
|
||||
|
||||
/**
|
||||
* Model with low prediction accuracy.
|
||||
*/
|
||||
const EVALUATE_LOW_SCORE = 4;
|
||||
|
||||
/**
|
||||
* Not enough data to evaluate the model properly.
|
||||
*/
|
||||
const EVALUATE_NOT_ENOUGH_DATA = 8;
|
||||
|
||||
const ANALYSE_REJECTED_RANGE_PROCESSOR = 4;
|
||||
/**
|
||||
* Invalid analysable for the time splitting method.
|
||||
*/
|
||||
const ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD = 4;
|
||||
|
||||
/**
|
||||
* Invalid analysable for all time splitting methods.
|
||||
*/
|
||||
const ANALYSABLE_STATUS_INVALID_FOR_RANGEPROCESSORS = 8;
|
||||
|
||||
/**
|
||||
* Invalid analysable for the target
|
||||
*/
|
||||
const ANALYSABLE_STATUS_INVALID_FOR_TARGET = 16;
|
||||
|
||||
/**
|
||||
* Minimum score to consider a non-static prediction model as good.
|
||||
*/
|
||||
const MIN_SCORE = 0.7;
|
||||
|
||||
/**
|
||||
* Maximum standard deviation between different evaluation repetitions to consider that evaluation results are stable.
|
||||
*/
|
||||
const ACCEPTED_DEVIATION = 0.05;
|
||||
|
||||
/**
|
||||
* Number of evaluation repetitions.
|
||||
*/
|
||||
const EVALUATION_ITERATIONS = 10;
|
||||
|
||||
/**
|
||||
@@ -552,7 +592,7 @@ class model {
|
||||
|
||||
if ($this->is_static()) {
|
||||
// Prediction based on assumptions.
|
||||
$result->status = \core_analytics\model::OK;
|
||||
$result->status = self::OK;
|
||||
$result->info = [];
|
||||
$result->predictions = $this->get_static_predictions($indicatorcalculations);
|
||||
|
||||
@@ -589,7 +629,7 @@ class model {
|
||||
if ($predictorresult->predictions) {
|
||||
foreach ($predictorresult->predictions as $sampleinfo) {
|
||||
|
||||
// We parse each prediction
|
||||
// We parse each prediction.
|
||||
switch (count($sampleinfo)) {
|
||||
case 1:
|
||||
// For whatever reason the predictions processor could not process this sample, we
|
||||
@@ -620,7 +660,7 @@ class model {
|
||||
* Execute the prediction callbacks defined by the target.
|
||||
*
|
||||
* @param \stdClass[] $predictions
|
||||
* @param array $predictions
|
||||
* @param array $indicatorcalculations
|
||||
* @return array
|
||||
*/
|
||||
protected function execute_prediction_callbacks($predictions, $indicatorcalculations) {
|
||||
@@ -636,8 +676,8 @@ class model {
|
||||
list($sampleid, $rangeindex) = $this->get_time_splitting()->infer_sample_info($uniquesampleid);
|
||||
|
||||
// Store the predicted values.
|
||||
$samplecontext = $this->save_prediction($sampleid, $rangeindex, $prediction->prediction, $prediction->predictionscore,
|
||||
json_encode($indicatorcalculations[$uniquesampleid]));
|
||||
$samplecontext = $this->save_prediction($sampleid, $rangeindex, $prediction->prediction,
|
||||
$prediction->predictionscore, json_encode($indicatorcalculations[$uniquesampleid]));
|
||||
|
||||
// Also store all samples context to later generate insights or whatever action the target wants to perform.
|
||||
$samplecontexts[$samplecontext->id] = $samplecontext;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Representation of a prediction.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -26,6 +27,7 @@ namespace core_analytics;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Representation of a prediction.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -33,12 +35,28 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class prediction {
|
||||
|
||||
/**
|
||||
* @var \stdClass
|
||||
*/
|
||||
private $prediction;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $sampledata;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $calculations = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \stdClass $prediction
|
||||
* @param array $sampledata
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($prediction, $sampledata) {
|
||||
global $DB;
|
||||
|
||||
@@ -52,18 +70,38 @@ class prediction {
|
||||
$this->format_calculations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prediction object data.
|
||||
*
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function get_prediction_data() {
|
||||
return $this->prediction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prediction sample data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sample_data() {
|
||||
return $this->sampledata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the prediction calculations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_calculations() {
|
||||
return $this->calculations;
|
||||
}
|
||||
|
||||
/**
|
||||
* format_calculations
|
||||
*
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
private function format_calculations() {
|
||||
|
||||
$calculations = json_decode($this->prediction->calculations, true);
|
||||
@@ -86,6 +124,12 @@ class prediction {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse_feature_name
|
||||
*
|
||||
* @param string $featurename
|
||||
* @return string[]
|
||||
*/
|
||||
private function parse_feature_name($featurename) {
|
||||
|
||||
$indicatorclass = $featurename;
|
||||
@@ -100,5 +144,4 @@ class prediction {
|
||||
|
||||
return array($indicatorclass, $subtype);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Representation of a suggested action associated with a prediction.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -26,6 +27,7 @@ namespace core_analytics;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Representation of a suggested action associated with a prediction.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -52,7 +54,8 @@ class prediction_action {
|
||||
public function __construct($actionname, \core_analytics\prediction $prediction, \moodle_url $actionurl, \pix_icon $icon, $text, $primary = false) {
|
||||
|
||||
// We want to track how effective are our suggested actions, we pass users through a script that will log these actions.
|
||||
$params = array('action' => $actionname, 'predictionid' => $prediction->get_prediction_data()->id, 'forwardurl' => $actionurl->out(false));
|
||||
$params = array('action' => $actionname, 'predictionid' => $prediction->get_prediction_data()->id,
|
||||
'forwardurl' => $actionurl->out(false));
|
||||
$url = new \moodle_url('/report/insights/action.php', $params);
|
||||
|
||||
if ($primary === false) {
|
||||
@@ -63,6 +66,8 @@ class prediction_action {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to the action.
|
||||
*
|
||||
* @return \action_menu_link
|
||||
*/
|
||||
public function get_action_link() {
|
||||
|
||||
@@ -35,11 +35,42 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
interface predictor {
|
||||
|
||||
/**
|
||||
* Is it ready to predict?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_ready();
|
||||
|
||||
/**
|
||||
* Train the provided dataset.
|
||||
*
|
||||
* @param int $modelid
|
||||
* @param \stored_file $dataset
|
||||
* @param string $outputdir
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function train($modelid, \stored_file $dataset, $outputdir);
|
||||
|
||||
/**
|
||||
* Predict the provided dataset samples.
|
||||
*
|
||||
* @param int $modelid
|
||||
* @param \stored_file $dataset
|
||||
* @param string $outputdir
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function predict($modelid, \stored_file $dataset, $outputdir);
|
||||
|
||||
/**
|
||||
* evaluate
|
||||
*
|
||||
* @param int $modelid
|
||||
* @param float $maxdeviation
|
||||
* @param int $niterations
|
||||
* @param \stored_file $dataset
|
||||
* @param string $outputdir
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function evaluate($modelid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Moodle site analysable.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
@@ -26,6 +27,7 @@ namespace core_analytics;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Moodle site analysable.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
|
||||
@@ -33,6 +33,11 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* availability_levels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function availability_levels() {
|
||||
return array(
|
||||
'activity' => array('activity'),
|
||||
@@ -51,7 +56,7 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
|
||||
list($course, $stu1) = $this->setup_course();
|
||||
|
||||
// forum1 is ignored as section 0 does not count.
|
||||
// Forum1 is ignored as section 0 does not count.
|
||||
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
|
||||
|
||||
$courseman = new \core_analytics\course($course);
|
||||
@@ -89,18 +94,27 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
$cm = $modinfo->get_cm($forum->cmid);
|
||||
|
||||
// Condition from after provided end time.
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-20 00:00:00 GMT'), strtotime('2015-10-21 00:00:00 GMT'), $stu1));
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-20 00:00:00 GMT'),
|
||||
strtotime('2015-10-21 00:00:00 GMT'), $stu1));
|
||||
|
||||
// Condition until before provided start time
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-25 00:00:00 GMT'), strtotime('2015-10-26 00:00:00 GMT'), $stu1));
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-25 00:00:00 GMT'),
|
||||
strtotime('2015-10-26 00:00:00 GMT'), $stu1));
|
||||
|
||||
// Condition until after provided end time.
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'), strtotime('2015-10-23 00:00:00 GMT'), $stu1));
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'),
|
||||
strtotime('2015-10-23 00:00:00 GMT'), $stu1));
|
||||
|
||||
// Condition until after provided start time and before provided end time.
|
||||
$this->assertCount(1, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'), strtotime('2015-10-25 00:00:00 GMT'), $stu1));
|
||||
$this->assertCount(1, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'),
|
||||
strtotime('2015-10-25 00:00:00 GMT'), $stu1));
|
||||
}
|
||||
|
||||
/**
|
||||
* test_get_activities_with_weeks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_get_activities_with_weeks() {
|
||||
|
||||
$startdate = gmmktime('0', '0', '0', 10, 24, 2015);
|
||||
@@ -112,7 +126,7 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
|
||||
list($course, $stu1) = $this->setup_course($record);
|
||||
|
||||
// forum1 is ignored as section 0 does not count.
|
||||
// Forum1 is ignored as section 0 does not count.
|
||||
$forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
||||
array('section' => 0));
|
||||
$forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
||||
@@ -138,9 +152,14 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
$this->assertCount(2, $courseman->get_activities('forum', $forth, $forth + WEEKSECS, $stu1));
|
||||
}
|
||||
|
||||
/**
|
||||
* test_get_activities_by_section
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_get_activities_by_section() {
|
||||
|
||||
// This makes debugging easier, sorry WA's +8 :)
|
||||
// This makes debugging easier, sorry WA's +8 :).
|
||||
$this->setTimezone('UTC');
|
||||
|
||||
// 1 year.
|
||||
@@ -156,7 +175,7 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
|
||||
list($course, $stu1) = $this->setup_course($record);
|
||||
|
||||
// forum1 is ignored as section 0 does not count.
|
||||
// Forum1 is ignored as section 0 does not count.
|
||||
$forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
||||
array('section' => 0));
|
||||
$forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
||||
@@ -187,8 +206,8 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
|
||||
// Split the course in as many parts as sections.
|
||||
$duration = ($enddate - $startdate) / $numsections;
|
||||
for($i = 1; $i <= $numsections; $i++) {
|
||||
// -1 because section 1 start represents the course start.
|
||||
for ($i = 1; $i <= $numsections; $i++) {
|
||||
// The -1 because section 1 start represents the course start.
|
||||
$timeranges[$i] = $startdate + ($duration * ($i - 1));
|
||||
}
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $timeranges[1], $timeranges[1] + $duration, $stu1));
|
||||
@@ -202,6 +221,12 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
$this->assertCount(0, $courseman->get_activities('forum', $timeranges[3], $timeranges[3] + $duration, $stu1));
|
||||
}
|
||||
|
||||
/**
|
||||
* setup_course
|
||||
*
|
||||
* @param stdClass $courserecord
|
||||
* @return array
|
||||
*/
|
||||
protected function setup_course($courserecord = null) {
|
||||
global $CFG;
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ class core_analytics_course_testcase extends advanced_testcase {
|
||||
$this->editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
||||
$this->teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
|
||||
|
||||
|
||||
$this->getDataGenerator()->enrol_user($this->stu1->id, $this->course->id, $this->studentroleid);
|
||||
$this->getDataGenerator()->enrol_user($this->stu2->id, $this->course->id, $this->studentroleid);
|
||||
$this->getDataGenerator()->enrol_user($this->both->id, $this->course->id, $this->studentroleid);
|
||||
@@ -69,7 +68,7 @@ class core_analytics_course_testcase extends advanced_testcase {
|
||||
$this->assertCount(2, $courseman->get_user_ids(array($this->editingteacherroleid)));
|
||||
$this->assertCount(1, $courseman->get_user_ids(array($this->teacherroleid)));
|
||||
|
||||
// Distinct is applied
|
||||
// Distinct is applied.
|
||||
$this->assertCount(3, $courseman->get_user_ids(array($this->editingteacherroleid, $this->teacherroleid)));
|
||||
$this->assertCount(4, $courseman->get_user_ids(array($this->editingteacherroleid, $this->studentroleid)));
|
||||
}
|
||||
|
||||
+50
-1
@@ -1,15 +1,65 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_indicator_fullname extends \core_analytics\local\indicator\linear {
|
||||
|
||||
/**
|
||||
* include_averages
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function include_averages() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
return array('course');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $samplesorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) {
|
||||
global $DB;
|
||||
|
||||
@@ -26,5 +76,4 @@ class test_indicator_fullname extends \core_analytics\local\indicator\linear {
|
||||
return self::MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+41
@@ -1,6 +1,47 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_indicator_max extends \core_analytics\local\indicator\binary {
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $samplesorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) {
|
||||
return self::MAX_VALUE;
|
||||
}
|
||||
|
||||
+41
@@ -1,6 +1,47 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_indicator_min extends \core_analytics\local\indicator\binary {
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $samplesorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) {
|
||||
return self::MIN_VALUE;
|
||||
}
|
||||
|
||||
+40
-2
@@ -1,10 +1,48 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_indicator_random extends \core_analytics\local\indicator\binary {
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $samplesorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $samplesorigin, $starttime, $endtime) {
|
||||
global $DB;
|
||||
|
||||
return mt_rand(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
+87
-12
@@ -1,13 +1,59 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Test static target.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test static target.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_static_target_shortname extends \core_analytics\local\target\binary {
|
||||
|
||||
/**
|
||||
* predictions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $predictions = array();
|
||||
|
||||
/**
|
||||
* get_analyser_class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_analyser_class() {
|
||||
return '\core_analytics\local\analyser\site_courses';
|
||||
}
|
||||
|
||||
/**
|
||||
* classes_description
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function classes_description() {
|
||||
return array(
|
||||
'Course fullname first char is A',
|
||||
@@ -17,7 +63,7 @@ class test_static_target_shortname extends \core_analytics\local\target\binary {
|
||||
|
||||
/**
|
||||
* We don't want to discard results.
|
||||
* @return float
|
||||
* @return null
|
||||
*/
|
||||
protected function min_prediction_score() {
|
||||
return null;
|
||||
@@ -31,24 +77,53 @@ class test_static_target_shortname extends \core_analytics\local\target\binary {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* is_valid_analysable
|
||||
*
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param bool $fortraining
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true) {
|
||||
// This is testing, let's make things easy.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) {
|
||||
global $DB;
|
||||
|
||||
$sample = $DB->get_record('course', array('id' => $sampleid));
|
||||
|
||||
if ($sample->visible == 0) {
|
||||
// We skip not-visible courses as a way to emulate the training data / prediction data difference.
|
||||
// In normal circumstances targets will return null when they receive a sample that can not be
|
||||
// processed, that same sample may be used for prediction.
|
||||
// We can not do this in is_valid_analysable because the analysable there is the site not the course.
|
||||
return null;
|
||||
/**
|
||||
* is_valid_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param bool $fortraining
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true) {
|
||||
// We skip not-visible courses during training as a way to emulate the training data / prediction data difference.
|
||||
// In normal circumstances is_valid_sample will return false when they receive a sample that can not be
|
||||
// processed.
|
||||
if (!$fortraining) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$sample = $this->retrieve('course', $sampleid);
|
||||
if ($sample->visible == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) {
|
||||
$sample = $this->retrieve('course', $sampleid);
|
||||
|
||||
$firstchar = substr($sample->shortname, 0, 1);
|
||||
if ($firstchar === 'a') {
|
||||
return 1;
|
||||
|
||||
@@ -1,13 +1,59 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Test target.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test target.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_target_shortname extends \core_analytics\local\target\binary {
|
||||
|
||||
/**
|
||||
* predictions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $predictions = array();
|
||||
|
||||
/**
|
||||
* get_analyser_class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_analyser_class() {
|
||||
return '\core_analytics\local\analyser\site_courses';
|
||||
}
|
||||
|
||||
/**
|
||||
* classes_description
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function classes_description() {
|
||||
return array(
|
||||
'Course fullname first char is A',
|
||||
@@ -31,11 +77,26 @@ class test_target_shortname extends \core_analytics\local\target\binary {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* is_valid_analysable
|
||||
*
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param bool $fortraining
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_analysable(\core_analytics\analysable $analysable, $fortraining = true) {
|
||||
// This is testing, let's make things easy.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* is_valid_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param bool $fortraining
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_sample($sampleid, \core_analytics\analysable $analysable, $fortraining = true) {
|
||||
// We skip not-visible courses during training as a way to emulate the training data / prediction data difference.
|
||||
// In normal circumstances is_valid_sample will return false when they receive a sample that can not be
|
||||
@@ -51,6 +112,15 @@ class test_target_shortname extends \core_analytics\local\target\binary {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false) {
|
||||
|
||||
$sample = $this->retrieve('course', $sampleid);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
/**
|
||||
* Unit tests for the model.
|
||||
*
|
||||
* @package analytics
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
@@ -32,7 +32,7 @@ require_once(__DIR__ . '/fixtures/test_target_shortname.php');
|
||||
/**
|
||||
* Unit tests for the model.
|
||||
*
|
||||
* @package analytics
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
@@ -142,12 +142,32 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testable version to change methods' visibility.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class testable_model extends \core_analytics\model {
|
||||
|
||||
/**
|
||||
* get_output_dir
|
||||
*
|
||||
* @param array $subdirs
|
||||
* @return string
|
||||
*/
|
||||
public function get_output_dir($subdirs = array()) {
|
||||
return parent::get_output_dir($subdirs);
|
||||
}
|
||||
|
||||
/**
|
||||
* init_analyser
|
||||
*
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public function init_analyser($options = array()) {
|
||||
return parent::init_analyser($options);
|
||||
parent::init_analyser($options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,12 @@ require_once(__DIR__ . '/fixtures/test_target_shortname.php');
|
||||
class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* test_ml_training_and_prediction
|
||||
*
|
||||
* @dataProvider provider_ml_training_and_prediction
|
||||
* @param string $timesplittingid
|
||||
* @param int $npredictedranges
|
||||
* @param string $predictionsprocessorclass
|
||||
* @return void
|
||||
*/
|
||||
public function test_ml_training_and_prediction($timesplittingid, $npredictedranges, $predictionsprocessorclass) {
|
||||
@@ -112,7 +115,7 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
// They will not be skipped for prediction though.
|
||||
$result = $model->predict();
|
||||
|
||||
// $course1 predictions should be 1 == 'a', $course2 predictions should be 0 == 'b'.
|
||||
// Var $course1 predictions should be 1 == 'a', $course2 predictions should be 0 == 'b'.
|
||||
$correct = array($course1->id => 1, $course2->id => 0);
|
||||
foreach ($result->predictions as $uniquesampleid => $predictiondata) {
|
||||
list($sampleid, $rangeindex) = $model->get_time_splitting()->infer_sample_info($uniquesampleid);
|
||||
@@ -127,7 +130,8 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$this->assertEquals(1, $DB->count_records('analytics_used_files',
|
||||
array('modelid' => $model->get_id(), 'action' => 'predicted')));
|
||||
// 2 predictions for each range.
|
||||
$this->assertEquals(2 * $npredictedranges, $DB->count_records('analytics_predictions', array('modelid' => $model->get_id())));
|
||||
$this->assertEquals(2 * $npredictedranges, $DB->count_records('analytics_predictions',
|
||||
array('modelid' => $model->get_id())));
|
||||
|
||||
// No new generated files nor records as there are no new courses available.
|
||||
$model->predict();
|
||||
@@ -135,9 +139,15 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$this->assertEquals($npredictedranges, count($trainedsamples));
|
||||
$this->assertEquals(1, $DB->count_records('analytics_used_files',
|
||||
array('modelid' => $model->get_id(), 'action' => 'predicted')));
|
||||
$this->assertEquals(2 * $npredictedranges, $DB->count_records('analytics_predictions', array('modelid' => $model->get_id())));
|
||||
$this->assertEquals(2 * $npredictedranges, $DB->count_records('analytics_predictions',
|
||||
array('modelid' => $model->get_id())));
|
||||
}
|
||||
|
||||
/**
|
||||
* provider_ml_training_and_prediction
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provider_ml_training_and_prediction() {
|
||||
$cases = array(
|
||||
'no_splitting' => array('\core_analytics\local\time_splitting\no_splitting', 1),
|
||||
@@ -153,6 +163,11 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
* Basic test to check that prediction processors work as expected.
|
||||
*
|
||||
* @dataProvider provider_ml_test_evaluation
|
||||
* @param string $modelquality
|
||||
* @param int $ncourses
|
||||
* @param array $expected
|
||||
* @param string $predictionsprocessorclass
|
||||
* @return void
|
||||
*/
|
||||
public function test_ml_evaluation($modelquality, $ncourses, $expected, $predictionsprocessorclass) {
|
||||
$this->resetAfterTest(true);
|
||||
@@ -172,7 +187,6 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
throw new \coding_exception('Only perfect and random accepted as $modelquality values');
|
||||
}
|
||||
|
||||
|
||||
// Generate training data.
|
||||
$params = array(
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
@@ -202,12 +216,19 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
// We check that the returned status includes at least $expectedcode code.
|
||||
foreach ($results as $timesplitting => $result) {
|
||||
$message = 'The returned status code ' . $result->status . ' should include ' . $expected[$timesplitting];
|
||||
$this->assertEquals($expected[$timesplitting], $result->status & $expected[$timesplitting], $message);
|
||||
$filtered = $result->status & $expected[$timesplitting];
|
||||
$this->assertEquals($expected[$timesplitting], $filtered, $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* provider_ml_test_evaluation
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provider_ml_test_evaluation() {
|
||||
|
||||
$notenoughandlowscore = \core_analytics\model::EVALUATE_NOT_ENOUGH_DATA + \core_analytics\model::EVALUATE_LOW_SCORE;
|
||||
$cases = array(
|
||||
'bad-and-no-enough-data' => array(
|
||||
'modelquality' => 'random',
|
||||
@@ -216,8 +237,8 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
// The course duration is too much to be processed by in weekly basis.
|
||||
'\core_analytics\local\time_splitting\weekly' => \core_analytics\model::NO_DATASET,
|
||||
// 10 samples is not enough to process anything.
|
||||
'\core_analytics\local\time_splitting\single_range' => \core_analytics\model::EVALUATE_NOT_ENOUGH_DATA + \core_analytics\model::EVALUATE_LOW_SCORE,
|
||||
'\core_analytics\local\time_splitting\quarters' => \core_analytics\model::EVALUATE_NOT_ENOUGH_DATA + \core_analytics\model::EVALUATE_LOW_SCORE,
|
||||
'\core_analytics\local\time_splitting\single_range' => $notenoughandlowscore,
|
||||
'\core_analytics\local\time_splitting\quarters' => $notenoughandlowscore,
|
||||
)
|
||||
),
|
||||
'bad' => array(
|
||||
@@ -244,6 +265,11 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
return $this->add_prediction_processors($cases);
|
||||
}
|
||||
|
||||
/**
|
||||
* add_random_model
|
||||
*
|
||||
* @return \core_analytics\model
|
||||
*/
|
||||
protected function add_random_model() {
|
||||
|
||||
$target = \core_analytics\manager::get_target('test_target_shortname');
|
||||
@@ -258,6 +284,11 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
return new \core_analytics\model($model->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* add_perfect_model
|
||||
*
|
||||
* @return \core_analytics\model
|
||||
*/
|
||||
protected function add_perfect_model() {
|
||||
|
||||
$target = \core_analytics\manager::get_target('test_target_shortname');
|
||||
@@ -272,6 +303,12 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
return new \core_analytics\model($model->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* add_prediction_processors
|
||||
*
|
||||
* @param array $cases
|
||||
* @return array
|
||||
*/
|
||||
protected function add_prediction_processors($cases) {
|
||||
|
||||
$return = array();
|
||||
|
||||
@@ -35,10 +35,20 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class no_teacher extends \core_analytics\local\indicator\binary {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:noteacher', 'moodle');
|
||||
}
|
||||
|
||||
/**
|
||||
* required_sample_data
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function required_sample_data() {
|
||||
// We require course because, although calculate_sample only reads context, we need the context to be course
|
||||
// or below.
|
||||
|
||||
@@ -83,6 +83,7 @@ class core_component {
|
||||
'MatthiasMullie\\Minify' => 'lib/minify/matthiasmullie-minify/src/',
|
||||
'MatthiasMullie\\PathConverter' => 'lib/minify/matthiasmullie-pathconverter/src/',
|
||||
'IMSGlobal\LTI' => 'lib/ltiprovider/src',
|
||||
'Phpml' => 'lib/mlbackend/php/phpml/src/Phpml',
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,7 +70,8 @@ class prediction_action_started extends \core\event\base {
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return "The user with id '$this->userid' has started '{$this->other['actionname']}' action for the prediction with id '".$this->objectid."'.";
|
||||
return "The user with id '$this->userid' has started '{$this->other['actionname']}' action for the prediction with id '" .
|
||||
$this->objectid . "'.";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +96,11 @@ class prediction_action_started extends \core\event\base {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get_objectid_mapping
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_objectid_mapping() {
|
||||
return array('db' => 'analytics_predictions');
|
||||
}
|
||||
|
||||
+4
-2
@@ -2084,7 +2084,8 @@ function xmldb_main_upgrade($oldversion) {
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
|
||||
// Adding indexes to table analytics_train_samples.
|
||||
$table->add_index('modelidandanalysableidandtimesplitting', XMLDB_INDEX_NOTUNIQUE, array('modelid', 'analysableid', 'timesplitting'));
|
||||
$table->add_index('modelidandanalysableidandtimesplitting', XMLDB_INDEX_NOTUNIQUE,
|
||||
array('modelid', 'analysableid', 'timesplitting'));
|
||||
|
||||
// Conditionally launch create table for analytics_train_samples.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
@@ -2106,7 +2107,8 @@ function xmldb_main_upgrade($oldversion) {
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
|
||||
// Adding indexes to table analytics_predict_ranges.
|
||||
$table->add_index('modelidandanalysableidandtimesplitting', XMLDB_INDEX_NOTUNIQUE, array('modelid', 'analysableid', 'timesplitting'));
|
||||
$table->add_index('modelidandanalysableidandtimesplitting', XMLDB_INDEX_NOTUNIQUE,
|
||||
array('modelid', 'analysableid', 'timesplitting'));
|
||||
|
||||
// Conditionally launch create table for analytics_predict_ranges.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
|
||||
+12
-5
@@ -1434,9 +1434,9 @@ function enrol_get_course_by_user_enrolment_id($ueid) {
|
||||
*
|
||||
* @param int $courseid Course id or false if using $uefilter (user enrolment ids may belong to different courses)
|
||||
* @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
|
||||
* @param array usersfilter Limit the results obtained to this list of user ids. $uefilter compatibility not guaranteed.
|
||||
* @param array uefilter Limit the results obtained to this list of user enrolment ids. $usersfilter compatibility not guaranteed.
|
||||
* @return stdClass
|
||||
* @param array $usersfilter Limit the results obtained to this list of user ids. $uefilter compatibility not guaranteed.
|
||||
* @param array $uefilter Limit the results obtained to this list of user enrolment ids. $usersfilter compatibility not guaranteed.
|
||||
* @return stdClass[]
|
||||
*/
|
||||
function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfilter = array(), $uefilter = array()) {
|
||||
global $DB;
|
||||
@@ -1460,8 +1460,10 @@ function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfi
|
||||
}
|
||||
|
||||
if ($onlyactive) {
|
||||
$conditions[] = "ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
|
||||
$params['now1'] = round(time(), -2); // improves db caching
|
||||
$conditions[] = "ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND " .
|
||||
"(ue.timeend = 0 OR ue.timeend > :now2)";
|
||||
// Improves db caching.
|
||||
$params['now1'] = round(time(), -2);
|
||||
$params['now2'] = $params['now1'];
|
||||
$params['active'] = ENROL_USER_ACTIVE;
|
||||
$params['enabled'] = ENROL_INSTANCE_ENABLED;
|
||||
@@ -1483,8 +1485,13 @@ function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfi
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrolment plugins abstract class.
|
||||
*
|
||||
* All enrol plugins should be based on this class,
|
||||
* this is also the main source of documentation.
|
||||
*
|
||||
* @copyright 2010 Petr Skoda {@link http://skodak.org}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class enrol_plugin {
|
||||
protected $config = null;
|
||||
|
||||
@@ -24,20 +24,12 @@
|
||||
|
||||
namespace mlbackend_php;
|
||||
|
||||
// TODO No support for 3rd party plugins psr4??
|
||||
spl_autoload_register(function($class) {
|
||||
// Autoload Phpml classes.
|
||||
$path = __DIR__ . '/../phpml/src/' . str_replace('\\', '/', $class) . '.php';
|
||||
if (file_exists($path)) {
|
||||
require_once($path);
|
||||
}
|
||||
});
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use Phpml\Preprocessing\Normalizer;
|
||||
use Phpml\CrossValidation\RandomSplit;
|
||||
use Phpml\Dataset\ArrayDataset;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use Phpml\ModelManager;
|
||||
|
||||
/**
|
||||
* PHP predictions processor.
|
||||
@@ -48,12 +40,31 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class processor implements \core_analytics\predictor {
|
||||
|
||||
/**
|
||||
* Size of training / prediction batches.
|
||||
*/
|
||||
const BATCH_SIZE = 5000;
|
||||
|
||||
/**
|
||||
* Number of train iterations.
|
||||
*/
|
||||
const TRAIN_ITERATIONS = 500;
|
||||
|
||||
/**
|
||||
* File name of the serialised model.
|
||||
*/
|
||||
const MODEL_FILENAME = 'model.ser';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $limitedsize = false;
|
||||
|
||||
/**
|
||||
* Checks if the processor is ready to use.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_ready() {
|
||||
if (version_compare(phpversion(), '7.0.0') < 0) {
|
||||
return get_string('errorphp7required', 'mlbackend_php');
|
||||
@@ -61,12 +72,20 @@ class processor implements \core_analytics\predictor {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trains a machine learning algorithm with the provided training set.
|
||||
*
|
||||
* @param string $uniqueid
|
||||
* @param \stored_file $dataset
|
||||
* @param string $outputdir
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function train($uniqueid, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Output directory is already unique to the model.
|
||||
$modelfilepath = $outputdir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME;
|
||||
|
||||
$modelmanager = new \Phpml\ModelManager();
|
||||
$modelmanager = new ModelManager();
|
||||
|
||||
if (file_exists($modelfilepath)) {
|
||||
$classifier = $modelmanager->restoreFromFile($modelfilepath);
|
||||
@@ -114,6 +133,14 @@ class processor implements \core_analytics\predictor {
|
||||
return $resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicts the provided samples
|
||||
*
|
||||
* @param string $uniqueid
|
||||
* @param \stored_file $dataset
|
||||
* @param string $outputdir
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function predict($uniqueid, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Output directory is already unique to the model.
|
||||
@@ -123,7 +150,7 @@ class processor implements \core_analytics\predictor {
|
||||
throw new \moodle_exception('errorcantloadmodel', 'analytics', '', $modelfilepath);
|
||||
}
|
||||
|
||||
$modelmanager = new \Phpml\ModelManager();
|
||||
$modelmanager = new ModelManager();
|
||||
$classifier = $modelmanager->restoreFromFile($modelfilepath);
|
||||
|
||||
$fh = $dataset->get_content_file_handle();
|
||||
@@ -204,7 +231,7 @@ class processor implements \core_analytics\predictor {
|
||||
|
||||
// Just an approximation, will depend on PHP version, compile options...
|
||||
// Double size + zval struct (6 bytes + 8 bytes + 16 bytes) + array bucket (96 bytes)
|
||||
// https://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html
|
||||
// https://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html.
|
||||
$floatsize = (PHP_INT_SIZE * 2) + 6 + 8 + 16 + 96;
|
||||
}
|
||||
|
||||
@@ -219,7 +246,7 @@ class processor implements \core_analytics\predictor {
|
||||
if (empty($CFG->mlbackend_php_no_evaluation_limits)) {
|
||||
// We allow admins to disable evaluation memory usage limits by modifying config.php.
|
||||
|
||||
// We will have plenty of missing values in the dataset so it should be a conservative approximation:
|
||||
// We will have plenty of missing values in the dataset so it should be a conservative approximation.
|
||||
$samplessize = $samplessize + (count($sampledata) * $floatsize);
|
||||
|
||||
// Stop fetching more samples.
|
||||
@@ -251,8 +278,17 @@ class processor implements \core_analytics\predictor {
|
||||
return $this->get_evaluation_result_object($dataset, $phis, $maxdeviation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the results objects from all evaluations.
|
||||
*
|
||||
* @param \stored_file $dataset
|
||||
* @param array $phis
|
||||
* @param float $maxdeviation
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function get_evaluation_result_object(\stored_file $dataset, $phis, $maxdeviation) {
|
||||
|
||||
// Average phi of all evaluations as final score.
|
||||
if (count($phis) === 1) {
|
||||
$avgphi = reset($phis);
|
||||
} else {
|
||||
@@ -300,6 +336,13 @@ class processor implements \core_analytics\predictor {
|
||||
return $resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Phi correlation coefficient.
|
||||
*
|
||||
* @param array $testlabels
|
||||
* @param array $predictedlabels
|
||||
* @return float
|
||||
*/
|
||||
protected function get_phi($testlabels, $predictedlabels) {
|
||||
|
||||
// Binary here only as well.
|
||||
@@ -320,6 +363,14 @@ class processor implements \core_analytics\predictor {
|
||||
return $phi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts metadata from the dataset file.
|
||||
*
|
||||
* The file poiter should be located at the top of the file.
|
||||
*
|
||||
* @param resource $fh
|
||||
* @return array
|
||||
*/
|
||||
protected function extract_metadata($fh) {
|
||||
$metadata = fgetcsv($fh);
|
||||
return array_combine($metadata, fgetcsv($fh));
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
<?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 'mlbackend_php'
|
||||
*
|
||||
* @package mlbackend_php
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['datasetsizelimited'] = 'Only a part of the evaluation dataset has been evaluated due to its size. Set $CFG->mlbackend_php_no_memory_limit if you are confident that your system can cope a {$a} dataset';
|
||||
$string['errorcantloadmodel'] = 'Model file {$a} does not exist, ensure the model has been trained before using it to predict.';
|
||||
|
||||
@@ -35,11 +35,19 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class processor implements \core_analytics\predictor {
|
||||
|
||||
/**
|
||||
* The required version of the python package that performs all calculations.
|
||||
*/
|
||||
const REQUIRED_PIP_PACKAGE_VERSION = '0.0.9';
|
||||
|
||||
/**
|
||||
* Is the plugin ready to be used?.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_ready() {
|
||||
|
||||
# Check the installed pip package version.
|
||||
// Check the installed pip package version.
|
||||
$cmd = 'python -m moodleinspire.version';
|
||||
|
||||
$output = null;
|
||||
@@ -63,8 +71,17 @@ class processor implements \core_analytics\predictor {
|
||||
return get_string('pythonpackagenotinstalled', 'mlbackend_python', $cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trains a machine learning algorithm with the provided dataset.
|
||||
*
|
||||
* @param string $uniqueid
|
||||
* @param \stored_file $dataset
|
||||
* @param string $outputdir
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function train($uniqueid, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Obtain the physical route to the file.
|
||||
$datasetpath = $this->get_file_path($dataset);
|
||||
|
||||
$cmd = 'python -m moodleinspire.training ' .
|
||||
@@ -95,8 +112,17 @@ class processor implements \core_analytics\predictor {
|
||||
return $resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns predictions for the provided dataset samples.
|
||||
*
|
||||
* @param string $uniqueid
|
||||
* @param \stored_file $dataset
|
||||
* @param string $outputdir
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function predict($uniqueid, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Obtain the physical route to the file.
|
||||
$datasetpath = $this->get_file_path($dataset);
|
||||
|
||||
$cmd = 'python -m moodleinspire.prediction ' .
|
||||
@@ -127,8 +153,19 @@ class processor implements \core_analytics\predictor {
|
||||
return $resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the provided dataset.
|
||||
*
|
||||
* @param string $uniqueid
|
||||
* @param float $maxdeviation
|
||||
* @param int $niterations
|
||||
* @param \stored_file $dataset
|
||||
* @param string $outputdir
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function evaluate($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Obtain the physical route to the file.
|
||||
$datasetpath = $this->get_file_path($dataset);
|
||||
|
||||
$cmd = 'python -m moodleinspire.evaluation ' .
|
||||
@@ -158,6 +195,12 @@ class processor implements \core_analytics\predictor {
|
||||
return $resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the dataset file.
|
||||
*
|
||||
* @param \stored_file $file
|
||||
* @return string
|
||||
*/
|
||||
protected function get_file_path(\stored_file $file) {
|
||||
// From moodle filesystem to the local file system.
|
||||
// This is not ideal, but there is no read access to moodle filesystem files.
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
<?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 'mlbackend_python'
|
||||
*
|
||||
* @package mlbackend_python
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['packageinstalledshouldbe'] = '"moodleinspire" python package should be updated. The required version is "{$a->required}" and the installed version is "{$a->installed}"';
|
||||
$string['pluginname'] = 'Python machine learning backend';
|
||||
|
||||
@@ -31,9 +31,11 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class core_component_testcase extends advanced_testcase {
|
||||
|
||||
// To be changed if number of subsystems increases/decreases,
|
||||
// this is defined here to annoy devs that try to add more without any thinking,
|
||||
// always verify that it does not collide with any existing add-on modules and subplugins!!!
|
||||
/**
|
||||
* To be changed if number of subsystems increases/decreases,
|
||||
* this is defined here to annoy devs that try to add more without any thinking,
|
||||
* always verify that it does not collide with any existing add-on modules and subplugins!!!
|
||||
*/
|
||||
const SUBSYSTEMCOUNT = 67;
|
||||
|
||||
public function setUp() {
|
||||
|
||||
@@ -35,10 +35,20 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
|
||||
|
||||
/**
|
||||
* feedback_viewed_events
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_viewed_events() {
|
||||
return array('\mod_assign\event\feedback_viewed');
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_check_grades
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_check_grades() {
|
||||
// We need the grade to be released to the student to consider that feedback has been provided.
|
||||
return true;
|
||||
|
||||
@@ -35,22 +35,52 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthassign', 'mod_assign');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_cognitive_depth_level(\cm_info $cm) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_submitted_events
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_submitted_events() {
|
||||
return array('\mod_assign\event\assessable_submitted');
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_replied
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @param int $contextid
|
||||
* @param int $userid
|
||||
* @param int $after
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_replied(\cm_info $cm, $contextid, $userid, $after = false) {
|
||||
// No level 4.
|
||||
return false;
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthassign', 'mod_assign');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthbook', 'mod_book');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_cognitive_depth_level(\cm_info $cm) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthbook', 'mod_book');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -35,11 +35,35 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
|
||||
|
||||
/**
|
||||
* feedback_viewed_events
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_viewed_events() {
|
||||
return array('\mod_chat\event\course_module_viewed', '\mod_chat\event\message_sent',
|
||||
'\mod_chat\event\sessions_viewed');
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_replied_events
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_replied_events() {
|
||||
return array('\mod_chat\event\message_sent');
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_post_action
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @param int $contextid
|
||||
* @param int $userid
|
||||
* @param string[] $eventnames
|
||||
* @param int $after
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $eventnames, $after = false) {
|
||||
|
||||
if (empty($this->activitylogs[$contextid][$userid])) {
|
||||
@@ -75,6 +99,11 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_check_grades
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_check_grades() {
|
||||
// Chat's feedback is not contained in grades.
|
||||
return false;
|
||||
|
||||
@@ -35,20 +35,31 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthchat', 'mod_chat');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
public function get_cognitive_depth_level(\cm_info $cm) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
protected function feedback_replied_events() {
|
||||
return array('\mod_chat\event\message_sent');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthchat', 'mod_chat');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,18 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
|
||||
|
||||
/**
|
||||
* choicedata
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $choicedata = array();
|
||||
|
||||
/**
|
||||
* feedback_viewed_events
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_viewed_events() {
|
||||
return array('\mod_choice\event\course_module_viewed', '\mod_choice\event\answer_updated');
|
||||
}
|
||||
@@ -51,10 +61,20 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o
|
||||
global $DB;
|
||||
|
||||
if (!isset($this->choicedata[$cm->instance])) {
|
||||
$this->choicedata[$cm->instance] = $DB->get_record('choice', array('id' => $cm->instance), 'id, showresults, timeclose', MUST_EXIST);
|
||||
$this->choicedata[$cm->instance] = $DB->get_record('choice', array('id' => $cm->instance),
|
||||
'id, showresults, timeclose', MUST_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_viewed
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @param int $contextid
|
||||
* @param int $userid
|
||||
* @param int $after
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_viewed(\cm_info $cm, $contextid, $userid, $after = null) {
|
||||
|
||||
// If results are shown after they answer a write action counts as feedback viewed.
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthchoice', 'mod_choice');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_cognitive_depth_level(\cm_info $cm) {
|
||||
$this->fill_choice_data($cm);
|
||||
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthchoice', 'mod_choice');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
$this->fill_choice_data($cm);
|
||||
return 2;
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthdata', 'mod_data');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
public function get_cognitive_depth_level(\cm_info $cm) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthdata', 'mod_data');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,12 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o
|
||||
*/
|
||||
protected $publishstats = array();
|
||||
|
||||
/**
|
||||
* fill_publishstats
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return void
|
||||
*/
|
||||
protected function fill_publishstats(\cm_info $cm) {
|
||||
global $DB;
|
||||
|
||||
@@ -55,7 +61,7 @@ abstract class activity_base extends \core_analytics\local\indicator\community_o
|
||||
* @param int $contextid
|
||||
* @param int $userid
|
||||
* @param int $after
|
||||
* @return int
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_viewed(\cm_info $cm, $contextid, $userid, $after = null) {
|
||||
// If stats are published any write action counts as viewed feedback.
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthfeedback', 'mod_feedback');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_cognitive_depth_level(\cm_info $cm) {
|
||||
$this->fill_publishstats($cm);
|
||||
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthfeedback', 'mod_feedback');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
$this->fill_publishstats($cm);
|
||||
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthfolder', 'mod_folder');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_cognitive_depth_level(\cm_info $cm) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthfolder', 'mod_folder');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -35,11 +35,27 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
|
||||
|
||||
/**
|
||||
* feedback_viewed_events
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_viewed_events() {
|
||||
// We could add any forum event, but it will make feedback_post_action slower
|
||||
return array('\mod_forum\event\assessable_uploaded', '\mod_forum\event\course_module_viewed', '\mod_forum\event\discussion_viewed');
|
||||
// We could add any forum event, but it will make feedback_post_action slower.
|
||||
return array('\mod_forum\event\assessable_uploaded', '\mod_forum\event\course_module_viewed',
|
||||
'\mod_forum\event\discussion_viewed');
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_post_action
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @param int $contextid
|
||||
* @param int $userid
|
||||
* @param string[] $eventnames
|
||||
* @param int $after
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_post_action(\cm_info $cm, $contextid, $userid, $eventnames, $after = false) {
|
||||
|
||||
if (empty($this->activitylogs[$contextid][$userid])) {
|
||||
|
||||
@@ -35,22 +35,48 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthforum', 'mod_forum');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
public function get_cognitive_depth_level(\cm_info $cm) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_check_grades
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function feedback_check_grades() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback_replied_events
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function feedback_replied_events() {
|
||||
return array('\mod_forum\event\assessable_uploaded');
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthforum', 'mod_forum');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class cognitive_depth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:cognitivedepthglossary', 'mod_glossary');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_COGNITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_cognitive_depth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
public function get_cognitive_depth_level(\cm_info $cm) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -35,14 +35,30 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class social_breadth extends activity_base {
|
||||
|
||||
/**
|
||||
* get_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('indicator:socialbreadthglossary', 'mod_glossary');
|
||||
}
|
||||
|
||||
/**
|
||||
* get_indicator_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indicator_type() {
|
||||
return self::INDICATOR_SOCIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_social_breadth_level
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return int
|
||||
*/
|
||||
protected function get_social_breadth_level(\cm_info $cm) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user