Merge branch 'MDL-60944_master' of git://github.com/dmonllao/moodle
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/str","core/log","core/notification","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={clear:{title:{key:"clearpredictions",component:"tool_analytics"},body:{key:"clearmodelpredictions",component:"tool_analytics"}}},h=function(b){return a(b.closest("tr")[0]).find("span.target-name").text()};return{confirmAction:function(i,j){a('[data-action-id="'+i+'"]').on("click",function(i){i.preventDefault();var k=a(i.currentTarget);if("undefined"==typeof g[j])return void c.error('Action "'+j+'" is not allowed.');var l=[g[j].title,g[j].body];l[1].param=h(k);var m=b.get_strings(l),n=e.create({type:e.types.SAVE_CANCEL});a.when(m,n).then(function(a,b){return b.setTitle(a[0]),b.setBody(a[1]),b.setSaveButtonText(a[0]),b.getRoot().on(f.save,function(){window.location.href=k.attr("href")}),b.show(),b}).fail(d.exception)})}}});
|
||||
define(["jquery","core/str","core/log","core/notification","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={clear:{title:{key:"clearpredictions",component:"tool_analytics"},body:{key:"clearmodelpredictions",component:"tool_analytics"}},"delete":{title:{key:"delete",component:"tool_analytics"},body:{key:"deletemodelconfirmation",component:"tool_analytics"}}},h=function(b){return a(b.closest("tr")[0]).find("span.target-name").text()};return{confirmAction:function(i,j){a('[data-action-id="'+i+'"]').on("click",function(i){i.preventDefault();var k=a(i.currentTarget);if("undefined"==typeof g[j])return void c.error('Action "'+j+'" is not allowed.');var l=[g[j].title,g[j].body];l[1].param=h(k);var m=b.get_strings(l),n=e.create({type:e.types.SAVE_CANCEL});a.when(m,n).then(function(a,b){return b.setTitle(a[0]),b.setBody(a[1]),b.setSaveButtonText(a[0]),b.getRoot().on(f.save,function(){window.location.href=k.attr("href")}),b.show(),b}).fail(d.exception)})}}});
|
||||
@@ -36,6 +36,15 @@ define(['jquery', 'core/str', 'core/log', 'core/notification', 'core/modal_facto
|
||||
component: 'tool_analytics'
|
||||
}
|
||||
|
||||
},
|
||||
'delete': {
|
||||
title: {
|
||||
key: 'delete',
|
||||
component: 'tool_analytics'
|
||||
}, body: {
|
||||
key: 'deletemodelconfirmation',
|
||||
component: 'tool_analytics'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -45,13 +45,25 @@ class edit_model extends \moodleform {
|
||||
|
||||
$mform = $this->_form;
|
||||
|
||||
if ($this->_customdata['model']->is_trained()) {
|
||||
if ($this->_customdata['trainedmodel']) {
|
||||
$message = get_string('edittrainedwarning', 'tool_analytics');
|
||||
$mform->addElement('html', $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING));
|
||||
}
|
||||
|
||||
$mform->addElement('advcheckbox', 'enabled', get_string('enabled', 'tool_analytics'));
|
||||
|
||||
if (!empty($this->_customdata['targets'])) {
|
||||
$targets = array('' => '');
|
||||
foreach ($this->_customdata['targets'] as $classname => $target) {
|
||||
$optionname = \tool_analytics\output\helper::class_to_option($classname);
|
||||
$targets[$optionname] = $target->get_name();
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'target', get_string('target', 'tool_analytics'), $targets);
|
||||
$mform->addHelpButton('target', 'target', 'tool_analytics');
|
||||
$mform->addRule('target', get_string('required'), 'required', null, 'client');
|
||||
}
|
||||
|
||||
$indicators = array();
|
||||
foreach ($this->_customdata['indicators'] as $classname => $indicator) {
|
||||
$optionname = \tool_analytics\output\helper::class_to_option($classname);
|
||||
@@ -88,11 +100,13 @@ class edit_model extends \moodleform {
|
||||
$predictionprocessors);
|
||||
$mform->addHelpButton('predictionsprocessor', 'predictionsprocessor', 'analytics');
|
||||
|
||||
$mform->addElement('hidden', 'id', $this->_customdata['id']);
|
||||
$mform->setType('id', PARAM_INT);
|
||||
if (!empty($this->_customdata['id'])) {
|
||||
$mform->addElement('hidden', 'id', $this->_customdata['id']);
|
||||
$mform->setType('id', PARAM_INT);
|
||||
|
||||
$mform->addElement('hidden', 'action', 'edit');
|
||||
$mform->setType('action', PARAM_ALPHANUMEXT);
|
||||
$mform->addElement('hidden', 'action', 'edit');
|
||||
$mform->setType('action', PARAM_ALPHANUMEXT);
|
||||
}
|
||||
|
||||
$this->add_action_buttons();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Model upload form.
|
||||
*
|
||||
* @package tool_analytics
|
||||
* @copyright 2017 onwards Ankit Agarwal
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_analytics\output\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Model upload form.
|
||||
*
|
||||
* @package tool_analytics
|
||||
* @copyright 2017 onwards Ankit Agarwal
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class import_model extends \moodleform {
|
||||
|
||||
/**
|
||||
* Form definition.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function definition () {
|
||||
$mform = $this->_form;
|
||||
|
||||
$mform->addElement('header', 'settingsheader', get_string('importmodel', 'tool_analytics'));
|
||||
|
||||
$mform->addElement('filepicker', 'modelfile', get_string('file'), null, ['accepted_types' => '.zip']);
|
||||
$mform->addRule('modelfile', null, 'required');
|
||||
|
||||
$mform->addElement('advcheckbox', 'ignoreversionmismatches', get_string('ignoreversionmismatches', 'tool_analytics'),
|
||||
get_string('ignoreversionmismatchescheckbox', 'tool_analytics'));
|
||||
|
||||
$this->add_action_buttons(true, get_string('import'));
|
||||
}
|
||||
}
|
||||
@@ -58,4 +58,38 @@ class helper {
|
||||
// Really unlikely but yeah, I'm a bad booyyy.
|
||||
return str_replace('2015102400ouuu', '\\', $option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an analytics > analytics models > $title breadcrumb.
|
||||
*
|
||||
* @param string $title
|
||||
* @param \moodle_url $url
|
||||
* @param \context|null $context Defaults to context_system
|
||||
* @return null
|
||||
*/
|
||||
public static function set_navbar(string $title, \moodle_url $url, ?\context $context = null) {
|
||||
global $PAGE;
|
||||
|
||||
if (!$context) {
|
||||
$context = \context_system::instance();
|
||||
}
|
||||
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url($url);
|
||||
|
||||
if ($siteadmin = $PAGE->settingsnav->find('root', \navigation_node::TYPE_SITE_ADMIN)) {
|
||||
$PAGE->navbar->add($siteadmin->get_content(), $siteadmin->action());
|
||||
}
|
||||
if ($analytics = $PAGE->settingsnav->find('analytics', \navigation_node::TYPE_SETTING)) {
|
||||
$PAGE->navbar->add($analytics->get_content(), $analytics->action());
|
||||
}
|
||||
if ($analyticmodels = $PAGE->settingsnav->find('analyticmodels', \navigation_node::TYPE_SETTING)) {
|
||||
$PAGE->navbar->add($analyticmodels->get_content(), $analyticmodels->action());
|
||||
}
|
||||
$PAGE->navbar->add($title, $url);
|
||||
|
||||
$PAGE->set_pagelayout('report');
|
||||
$PAGE->set_title($title);
|
||||
$PAGE->set_heading($title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ class models_list implements \renderable, \templatable {
|
||||
global $PAGE;
|
||||
|
||||
$data = new \stdClass();
|
||||
$data->importmodelurl = new \moodle_url('/admin/tool/analytics/importmodel.php');
|
||||
$data->createmodelurl = new \moodle_url('/admin/tool/analytics/createmodel.php');
|
||||
|
||||
$onlycli = get_config('analytics', 'onlycli');
|
||||
if ($onlycli === false) {
|
||||
@@ -229,10 +231,19 @@ class models_list implements \renderable, \templatable {
|
||||
|
||||
// Export training data.
|
||||
if (!$model->is_static() && $model->is_trained()) {
|
||||
$urlparams['action'] = 'export';
|
||||
$urlparams['action'] = 'exportdata';
|
||||
$url = new \moodle_url('model.php', $urlparams);
|
||||
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/export',
|
||||
get_string('exporttrainingdata', 'tool_analytics')), get_string('export', 'tool_analytics'));
|
||||
get_string('exporttrainingdata', 'tool_analytics')), get_string('exporttrainingdata', 'tool_analytics'));
|
||||
$actionsmenu->add($icon);
|
||||
}
|
||||
|
||||
// Export model.
|
||||
if (!$model->is_static() && $model->get_indicators() && !empty($modeldata->timesplitting)) {
|
||||
$urlparams['action'] = 'exportmodel';
|
||||
$url = new \moodle_url('model.php', $urlparams);
|
||||
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/backup',
|
||||
get_string('exportmodel', 'tool_analytics')), get_string('exportmodel', 'tool_analytics'));
|
||||
$actionsmenu->add($icon);
|
||||
}
|
||||
|
||||
@@ -258,6 +269,15 @@ class models_list implements \renderable, \templatable {
|
||||
$actionsmenu->add($icon);
|
||||
}
|
||||
|
||||
$actionid = 'delete-' . $model->get_id();
|
||||
$PAGE->requires->js_call_amd('tool_analytics/model', 'confirmAction', [$actionid, 'delete']);
|
||||
$urlparams['action'] = 'delete';
|
||||
$url = new \moodle_url('model.php', $urlparams);
|
||||
$icon = new \action_menu_link_secondary($url, new \pix_icon('t/delete',
|
||||
get_string('delete', 'tool_analytics')), get_string('delete', 'tool_analytics'),
|
||||
['data-action-id' => $actionid]);
|
||||
$actionsmenu->add($icon);
|
||||
|
||||
$modeldata->actions = $actionsmenu->export_for_template($output);
|
||||
|
||||
$data->models[] = $modeldata;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Create model form.
|
||||
*
|
||||
* @package tool_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login();
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
|
||||
$returnurl = new \moodle_url('/admin/tool/analytics/index.php');
|
||||
$url = new \moodle_url('/admin/tool/analytics/createmodel.php');
|
||||
$title = get_string('createmodel', 'tool_analytics');
|
||||
|
||||
\tool_analytics\output\helper::set_navbar($title, $url);
|
||||
|
||||
// Static targets are not editable, we discard them.
|
||||
$targets = array_filter(\core_analytics\manager::get_all_targets(), function($target) {
|
||||
return (!$target->based_on_assumptions());
|
||||
});
|
||||
|
||||
$customdata = array(
|
||||
'trainedmodel' => false,
|
||||
'targets' => $targets,
|
||||
'indicators' => \core_analytics\manager::get_all_indicators(),
|
||||
'timesplittings' => \core_analytics\manager::get_enabled_time_splitting_methods(),
|
||||
'predictionprocessors' => \core_analytics\manager::get_all_prediction_processors(),
|
||||
);
|
||||
$mform = new \tool_analytics\output\form\edit_model(null, $customdata);
|
||||
|
||||
if ($mform->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
|
||||
} else if ($data = $mform->get_data()) {
|
||||
|
||||
// Converting option names to class names.
|
||||
$targetclass = \tool_analytics\output\helper::option_to_class($data->target);
|
||||
if (empty($targets[$targetclass])) {
|
||||
throw new \moodle_exception('errorinvalidtarget', 'analytics', '', $targetclass);
|
||||
}
|
||||
$target = $targets[$targetclass];
|
||||
|
||||
$indicators = array();
|
||||
foreach ($data->indicators as $indicator) {
|
||||
$indicatorinstance = \core_analytics\manager::get_indicator(
|
||||
\tool_analytics\output\helper::option_to_class($indicator)
|
||||
);
|
||||
$indicators[$indicatorinstance->get_id()] = $indicatorinstance;
|
||||
}
|
||||
$timesplitting = \tool_analytics\output\helper::option_to_class($data->timesplitting);
|
||||
$predictionsprocessor = \tool_analytics\output\helper::option_to_class($data->predictionsprocessor);
|
||||
|
||||
// Insert the model into db.
|
||||
$model = \core_analytics\model::create($target, []);
|
||||
|
||||
// Filter out indicators that can not be used by this target.
|
||||
$invalidindicators = array_diff_key($indicators, $model->get_potential_indicators());
|
||||
if ($invalidindicators) {
|
||||
$indicators = array_diff_key($indicators, $invalidindicators);
|
||||
}
|
||||
|
||||
// Update the model with the valid list of indicators.
|
||||
$model->update($data->enabled, $indicators, $timesplitting, $predictionsprocessor);
|
||||
|
||||
$message = '';
|
||||
$messagetype = \core\output\notification::NOTIFY_SUCCESS;
|
||||
if (!empty($invalidindicators)) {
|
||||
$message = get_string('invalidindicatorsremoved', 'tool_analytics');
|
||||
}
|
||||
redirect($returnurl, $message, 0, $messagetype);
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
$mform->display();
|
||||
echo $OUTPUT->footer();
|
||||
@@ -0,0 +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/>.
|
||||
|
||||
/**
|
||||
* Import models tool frontend.
|
||||
*
|
||||
* @package tool_analytics
|
||||
* @copyright 2017 onwards Ankit Agarwal
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login();
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
|
||||
$returnurl = new \moodle_url('/admin/tool/analytics/index.php');
|
||||
$url = new \moodle_url('/admin/tool/analytics/importmodel.php');
|
||||
$title = get_string('importmodel', 'tool_analytics');
|
||||
|
||||
\tool_analytics\output\helper::set_navbar($title, $url);
|
||||
|
||||
$form = new \tool_analytics\output\form\import_model();
|
||||
if ($form->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
} else if ($data = $form->get_data()) {
|
||||
|
||||
$modelconfig = new \core_analytics\model_config();
|
||||
|
||||
$zipfilepath = $form->save_temp_file('modelfile');
|
||||
|
||||
list ($modeldata, $unused) = $modelconfig->extract_import_contents($zipfilepath);
|
||||
|
||||
if ($error = $modelconfig->check_dependencies($modeldata, $data->ignoreversionmismatches)) {
|
||||
// The file is not available until the form is validated so we need an alternative method to show errors.
|
||||
redirect($url, $error, 0, \core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
\core_analytics\model::import_model($zipfilepath);
|
||||
|
||||
redirect($returnurl, get_string('importedsuccessfully', 'tool_analytics'), 0,
|
||||
\core\output\notification::NOTIFY_SUCCESS);
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
$form->display();
|
||||
echo $OUTPUT->footer();
|
||||
@@ -29,11 +29,15 @@ $string['analyticmodels'] = 'Analytics models';
|
||||
$string['bettercli'] = 'Evaluating models and generating predictions may involve heavy processing. It is recommended to run these actions from the command line.';
|
||||
$string['cantguessstartdate'] = 'Can\'t guess the start date';
|
||||
$string['cantguessenddate'] = 'Can\'t guess the end date';
|
||||
$string['classdoesnotexist'] = 'Class {$a} does not exist';
|
||||
$string['clearpredictions'] = 'Clear predictions';
|
||||
$string['clearmodelpredictions'] = 'Are you sure you want to clear all "{$a}" predictions?';
|
||||
$string['clienablemodel'] = 'You can enable the model by selecting a time-splitting method by its ID. Note that you can also enable it later using the web interface (\'none\' to exit).';
|
||||
$string['clievaluationandpredictions'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. You can allow these processes to be executed manually via the web interface by disabling the <a href="{$a}">\'onlycli\'</a> analytics setting.';
|
||||
$string['clievaluationandpredictionsnoadmin'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. It may be enabled by a site administrator.';
|
||||
$string['createmodel'] = 'Create model';
|
||||
$string['delete'] = 'Delete';
|
||||
$string['deletemodelconfirmation'] = 'Are you sure you want to delete "{$a}"? These changes can not be reverted.';
|
||||
$string['disabled'] = 'Disabled';
|
||||
$string['editmodel'] = 'Edit "{$a}" model';
|
||||
$string['edittrainedwarning'] = 'This model has already been trained. Note that changing its indicators or its time-splitting method will delete its previous predictions and start generating new predictions.';
|
||||
@@ -50,7 +54,7 @@ $string['errortrainingdataexport'] = 'The model training data could not be expor
|
||||
$string['evaluate'] = 'Evaluate';
|
||||
$string['evaluatemodel'] = 'Evaluate model';
|
||||
$string['evaluationinbatches'] = 'The site contents are calculated and stored in batches. The evaluation process may be stopped at any time. The next time it is run, it will continue from the point when it was stopped.';
|
||||
$string['export'] = 'Export';
|
||||
$string['exportmodel'] = 'Export configuration';
|
||||
$string['exporttrainingdata'] = 'Export training data';
|
||||
$string['getpredictionsresultscli'] = 'Results using {$a->name} (id: {$a->id}) course duration splitting';
|
||||
$string['getpredictionsresults'] = 'Results using {$a->name} course duration splitting';
|
||||
@@ -58,15 +62,21 @@ $string['extrainfo'] = 'Info';
|
||||
$string['generalerror'] = 'Evaluation error. Status code {$a}';
|
||||
$string['getpredictions'] = 'Get predictions';
|
||||
$string['goodmodel'] = 'This is a good model for using to obtain predictions. Enable it to start obtaining predictions.';
|
||||
$string['importmodel'] = 'Import model';
|
||||
$string['indicators'] = 'Indicators';
|
||||
$string['info'] = 'Info';
|
||||
$string['ignoreversionmismatches'] = 'Ignore version mismatches';
|
||||
$string['ignoreversionmismatchescheckbox'] = 'Ignore the differences between this site version and the original site version.';
|
||||
$string['importedsuccessfully'] = 'The model has been successfully imported.';
|
||||
$string['insights'] = 'Insights';
|
||||
$string['invalidanalysables'] = 'Invalid site elements';
|
||||
$string['invalidanalysablesinfo'] = 'This pages lists this site analysable elements that can not be used by this prediction model. The listed elements can not be used neither to train the prediction model nor the prediction model can get predictions for them.';
|
||||
$string['invalidanalysablestable'] = 'Invalid site analysable elements table';
|
||||
$string['invalidindicatorsremoved'] = 'A new model has been added. Indicators that do not work with the selected target have been automatically removed.';
|
||||
$string['invalidprediction'] = 'Invalid to get predictions';
|
||||
$string['invalidtraining'] = 'Invalid to train the model';
|
||||
$string['loginfo'] = 'Log extra info';
|
||||
$string['missingmoodleversion'] = 'Imported file does not define a moodle version number';
|
||||
$string['modelid'] = 'Model ID';
|
||||
$string['modelinvalidanalysables'] = 'Invalid analysable elements for "{$a}" model';
|
||||
$string['modelresults'] = '{$a} results';
|
||||
@@ -86,12 +96,14 @@ $string['previouspage'] = 'Previous page';
|
||||
$string['samestartdate'] = 'Current start date is good';
|
||||
$string['sameenddate'] = 'Current end date is good';
|
||||
$string['target'] = 'Target';
|
||||
$string['target_help'] = 'The target is what the model will predict.';
|
||||
$string['timesplittingnotdefined'] = 'Time splitting is not defined.';
|
||||
$string['timesplittingnotdefined_help'] = 'You need to select a time-splitting method before enabling the model.';
|
||||
$string['trainandpredictmodel'] = 'Training model and calculating predictions';
|
||||
$string['trainingprocessfinished'] = 'Training process finished';
|
||||
$string['trainingresults'] = 'Training results';
|
||||
$string['trainmodels'] = 'Train models';
|
||||
$string['versionnotsame'] = 'Imported file was from a different moodle version ({$a->importedversion}) than the current one ({$a->version})';
|
||||
$string['viewlog'] = 'Log';
|
||||
$string['weeksenddateautomaticallyset'] = 'End date automatically set based on start date and the number of sections';
|
||||
$string['weeksenddatedefault'] = 'End date automatically calculated from the course start date.';
|
||||
|
||||
@@ -23,17 +23,17 @@
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
|
||||
$id = required_param('id', PARAM_INT);
|
||||
$action = required_param('action', PARAM_ALPHANUMEXT);
|
||||
|
||||
$context = context_system::instance();
|
||||
|
||||
require_login();
|
||||
|
||||
$model = new \core_analytics\model($id);
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
|
||||
$returnurl = new \moodle_url('/admin/tool/analytics/index.php');
|
||||
$params = array('id' => $id, 'action' => $action);
|
||||
$url = new \moodle_url('/admin/tool/analytics/model.php', $params);
|
||||
|
||||
@@ -57,8 +57,14 @@ switch ($action) {
|
||||
case 'disable':
|
||||
$title = get_string('disable');
|
||||
break;
|
||||
case 'export':
|
||||
$title = get_string('export', 'tool_analytics');
|
||||
case 'delete':
|
||||
$title = get_string('delete');
|
||||
break;
|
||||
case 'exportdata':
|
||||
$title = get_string('exporttrainingdata', 'tool_analytics');
|
||||
break;
|
||||
case 'exportmodel':
|
||||
$title = get_string('exportmodel', 'tool_analytics');
|
||||
break;
|
||||
case 'clear':
|
||||
$title = get_string('clearpredictions', 'tool_analytics');
|
||||
@@ -70,11 +76,7 @@ switch ($action) {
|
||||
throw new moodle_exception('errorunknownaction', 'analytics');
|
||||
}
|
||||
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_pagelayout('report');
|
||||
$PAGE->set_title($title);
|
||||
$PAGE->set_heading($title);
|
||||
\tool_analytics\output\helper::set_navbar($title, $url);
|
||||
|
||||
$onlycli = get_config('analytics', 'onlycli');
|
||||
if ($onlycli === false) {
|
||||
@@ -88,14 +90,21 @@ switch ($action) {
|
||||
confirm_sesskey();
|
||||
|
||||
$model->enable();
|
||||
redirect(new \moodle_url('/admin/tool/analytics/index.php'));
|
||||
redirect($returnurl);
|
||||
break;
|
||||
|
||||
case 'disable':
|
||||
confirm_sesskey();
|
||||
|
||||
$model->update(0, false, false);
|
||||
redirect(new \moodle_url('/admin/tool/analytics/index.php'));
|
||||
redirect($returnurl);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
confirm_sesskey();
|
||||
|
||||
$model->delete();
|
||||
redirect($returnurl);
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
@@ -108,7 +117,7 @@ switch ($action) {
|
||||
|
||||
$customdata = array(
|
||||
'id' => $model->get_id(),
|
||||
'model' => $model,
|
||||
'trainedmodel' => $model->is_trained(),
|
||||
'indicators' => $model->get_potential_indicators(),
|
||||
'timesplittings' => \core_analytics\manager::get_enabled_time_splitting_methods(),
|
||||
'predictionprocessors' => \core_analytics\manager::get_all_prediction_processors()
|
||||
@@ -116,7 +125,7 @@ switch ($action) {
|
||||
$mform = new \tool_analytics\output\form\edit_model(null, $customdata);
|
||||
|
||||
if ($mform->is_cancelled()) {
|
||||
redirect(new \moodle_url('/admin/tool/analytics/index.php'));
|
||||
redirect($returnurl);
|
||||
|
||||
} else if ($data = $mform->get_data()) {
|
||||
|
||||
@@ -129,7 +138,7 @@ switch ($action) {
|
||||
$timesplitting = \tool_analytics\output\helper::option_to_class($data->timesplitting);
|
||||
$predictionsprocessor = \tool_analytics\output\helper::option_to_class($data->predictionsprocessor);
|
||||
$model->update($data->enabled, $indicators, $timesplitting, $predictionsprocessor);
|
||||
redirect(new \moodle_url('/admin/tool/analytics/index.php'));
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
@@ -203,7 +212,7 @@ switch ($action) {
|
||||
echo $renderer->render_table($modellogstable);
|
||||
break;
|
||||
|
||||
case 'export':
|
||||
case 'exportdata':
|
||||
|
||||
if ($model->is_static() || !$model->is_trained()) {
|
||||
throw new moodle_exception('errornoexport', 'tool_analytics');
|
||||
@@ -211,7 +220,7 @@ switch ($action) {
|
||||
|
||||
$file = $model->get_training_data();
|
||||
if (!$file) {
|
||||
redirect(new \moodle_url('/admin/tool/analytics/index.php'), get_string('errortrainingdataexport', 'tool_analytics'),
|
||||
redirect($returnurl, get_string('errortrainingdataexport', 'tool_analytics'),
|
||||
null, \core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
|
||||
@@ -219,11 +228,17 @@ switch ($action) {
|
||||
send_file($file, $filename, null, 0, false, true);
|
||||
break;
|
||||
|
||||
case 'exportmodel':
|
||||
$zipfilename = 'model-' . $model->get_unique_id() . '-' . microtime(false) . '.zip';
|
||||
$zipfilepath = $model->export_model($zipfilename);
|
||||
send_temp_file($zipfilepath, $zipfilename);
|
||||
break;
|
||||
|
||||
case 'clear':
|
||||
confirm_sesskey();
|
||||
|
||||
$model->clear();
|
||||
redirect(new \moodle_url('/admin/tool/analytics/index.php'));
|
||||
redirect($returnurl);
|
||||
break;
|
||||
|
||||
case 'invalidanalysables':
|
||||
|
||||
@@ -110,6 +110,10 @@
|
||||
{{/infos}}
|
||||
|
||||
<div class="box">
|
||||
<div class="top-nav d-flex">
|
||||
<a href="{{createmodelurl}}" class="btn btn-default mr-2">{{#str}}createmodel, tool_analytics{{/str}}</a>
|
||||
<a href="{{importmodelurl}}" class="btn btn-default">{{#str}}importmodel, tool_analytics{{/str}}</a>
|
||||
</div>
|
||||
<table class="generaltable fullwidth">
|
||||
<caption>{{#str}}analyticmodels, tool_analytics{{/str}}</caption>
|
||||
<thead>
|
||||
|
||||
@@ -82,6 +82,7 @@ abstract class calculable {
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
// Using get_class as get_component_classes_in_namespace returns double escaped fully qualified class names.
|
||||
return '\\' . get_class($this);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,11 @@ class manager {
|
||||
*/
|
||||
protected static $predictionprocessors = null;
|
||||
|
||||
/**
|
||||
* @var \core_analytics\local\target\base[]
|
||||
*/
|
||||
protected static $alltargets = null;
|
||||
|
||||
/**
|
||||
* @var \core_analytics\local\indicator\base[]
|
||||
*/
|
||||
@@ -281,6 +286,28 @@ class manager {
|
||||
return new $fullclassname();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all targets in the system.
|
||||
*
|
||||
* @return \core_analytics\local\target\base[]
|
||||
*/
|
||||
public static function get_all_targets() : array {
|
||||
if (self::$alltargets !== null) {
|
||||
return self::$alltargets;
|
||||
}
|
||||
|
||||
$classes = self::get_analytics_classes('target');
|
||||
|
||||
self::$alltargets = [];
|
||||
foreach ($classes as $fullclassname => $classpath) {
|
||||
$instance = self::get_target($fullclassname);
|
||||
if ($instance) {
|
||||
self::$alltargets[$instance->get_id()] = $instance;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$alltargets;
|
||||
}
|
||||
/**
|
||||
* Return all system indicators.
|
||||
*
|
||||
@@ -297,7 +324,6 @@ class manager {
|
||||
foreach ($classes as $fullclassname => $classpath) {
|
||||
$instance = self::get_indicator($fullclassname);
|
||||
if ($instance) {
|
||||
// Using get_class as get_component_classes_in_namespace returns double escaped fully qualified class names.
|
||||
self::$allindicators[$instance->get_id()] = $instance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,11 +338,12 @@ class model {
|
||||
*
|
||||
* @param \core_analytics\local\target\base $target
|
||||
* @param \core_analytics\local\indicator\base[] $indicators
|
||||
* @param string $timesplittingid The time splitting method id (its fully qualified class name)
|
||||
* @param string|false $timesplittingid The time splitting method id (its fully qualified class name)
|
||||
* @param string|null $processor The machine learning backend this model will use.
|
||||
* @return \core_analytics\model
|
||||
*/
|
||||
public static function create(\core_analytics\local\target\base $target, array $indicators,
|
||||
$timesplittingid = false, $processor = false) {
|
||||
$timesplittingid = false, $processor = null) {
|
||||
global $USER, $DB;
|
||||
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
@@ -360,8 +361,8 @@ class model {
|
||||
$modelobj->usermodified = $USER->id;
|
||||
|
||||
if ($processor &&
|
||||
!self::is_valid($processor, '\core_analytics\classifier') &&
|
||||
!self::is_valid($processor, '\core_analytics\regressor')) {
|
||||
!manager::is_valid($processor, '\core_analytics\classifier') &&
|
||||
!manager::is_valid($processor, '\core_analytics\regressor')) {
|
||||
throw new \coding_exception('The provided predictions processor \\' . $processor . '\processor is not valid');
|
||||
} else {
|
||||
$modelobj->predictionsprocessor = $processor;
|
||||
@@ -462,6 +463,7 @@ class model {
|
||||
|
||||
// It needs to be reset as the version changes.
|
||||
$this->uniqueid = null;
|
||||
$this->indicators = null;
|
||||
|
||||
// We update the version of the model so different time splittings are not mixed up.
|
||||
$this->model->version = $now;
|
||||
@@ -1308,7 +1310,7 @@ class model {
|
||||
* @param bool $onlymodelid Preference over $subdirs
|
||||
* @return string
|
||||
*/
|
||||
protected function get_output_dir($subdirs = array(), $onlymodelid = false) {
|
||||
public function get_output_dir($subdirs = array(), $onlymodelid = false) {
|
||||
global $CFG;
|
||||
|
||||
$subdirstr = '';
|
||||
@@ -1357,7 +1359,7 @@ class model {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the model data.
|
||||
* Exports the model data for displaying it in a template.
|
||||
*
|
||||
* @return \stdClass
|
||||
*/
|
||||
@@ -1379,6 +1381,58 @@ class model {
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the model data to a zip file.
|
||||
*
|
||||
* @param string $zipfilename
|
||||
* @return string Zip file path
|
||||
*/
|
||||
public function export_model(string $zipfilename) : string {
|
||||
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
|
||||
$modelconfig = new model_config($this);
|
||||
return $modelconfig->export($zipfilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the provided model.
|
||||
*
|
||||
* Note that this method assumes that model_config::check_dependencies has already been called.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param string $zipfilepath Zip file path
|
||||
* @return \core_analytics\model
|
||||
*/
|
||||
public static function import_model(string $zipfilepath) : \core_analytics\model {
|
||||
|
||||
\core_analytics\manager::check_can_manage_models();
|
||||
|
||||
$modelconfig = new \core_analytics\model_config();
|
||||
return $modelconfig->import($zipfilepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this model be exported?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_export_configuration() : bool {
|
||||
|
||||
if (empty($this->model->timesplitting)) {
|
||||
return false;
|
||||
}
|
||||
if (!$this->get_indicators()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->is_static()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model logs data.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Model configuration manager.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_analytics;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Model configuration manager.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class model_config {
|
||||
|
||||
/**
|
||||
* @var \core_analytics\model
|
||||
*/
|
||||
private $model = null;
|
||||
|
||||
/**
|
||||
* The name of the file where config is held.
|
||||
*/
|
||||
const CONFIG_FILE_NAME = 'model-config.json';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \core_analytics\model|null $model
|
||||
*/
|
||||
public function __construct(?model $model = null) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a model to a zip using the provided file name.
|
||||
*
|
||||
* @param string $zipfilename
|
||||
* @return string
|
||||
*/
|
||||
public function export(string $zipfilename) : string {
|
||||
|
||||
if (!$this->model) {
|
||||
throw new \coding_exception('No model object provided.');
|
||||
}
|
||||
|
||||
if (!$this->model->can_export_configuration()) {
|
||||
throw new \moodle_exception('errornoexportconfigrequirements', 'analytics');
|
||||
}
|
||||
|
||||
$zip = new \zip_packer();
|
||||
$zipfiles = [];
|
||||
|
||||
// Model config in JSON.
|
||||
$modeldata = $this->export_model_data();
|
||||
|
||||
$exporttmpdir = make_request_directory('analyticsexport');
|
||||
$jsonfilepath = $exporttmpdir . DIRECTORY_SEPARATOR . 'model-config.json';
|
||||
if (!file_put_contents($jsonfilepath, json_encode($modeldata))) {
|
||||
print_error('errornoexportconfig', 'analytics');
|
||||
}
|
||||
$zipfiles[self::CONFIG_FILE_NAME] = $jsonfilepath;
|
||||
|
||||
// ML backend.
|
||||
if ($this->model->is_trained()) {
|
||||
$processor = $this->model->get_predictions_processor(true);
|
||||
$outputdir = $this->model->get_output_dir(array('execution'));
|
||||
$mlbackenddir = $processor->export($this->model->get_unique_id(), $outputdir);
|
||||
$mlbackendfiles = get_directory_list($mlbackenddir);
|
||||
foreach ($mlbackendfiles as $mlbackendfile) {
|
||||
$fullpath = $mlbackenddir . DIRECTORY_SEPARATOR . $mlbackendfile;
|
||||
// Place the ML backend files inside a mlbackend/ dir.
|
||||
$zipfiles['mlbackend/' . $mlbackendfile] = $fullpath;
|
||||
}
|
||||
}
|
||||
|
||||
$zipfilepath = $exporttmpdir . DIRECTORY_SEPARATOR . $zipfilename;
|
||||
$zip->archive_to_pathname($zipfiles, $zipfilepath);
|
||||
|
||||
return $zipfilepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the provided model configuration into a new model.
|
||||
*
|
||||
* Note that this method assumes that self::check_dependencies has already been called.
|
||||
*
|
||||
* @param string $zipfilepath Path to the zip file to import
|
||||
* @return \core_analytics\model
|
||||
*/
|
||||
public function import(string $zipfilepath) : \core_analytics\model {
|
||||
|
||||
list($modeldata, $mlbackenddir) = $this->extract_import_contents($zipfilepath);
|
||||
|
||||
$target = \core_analytics\manager::get_target($modeldata->target);
|
||||
$indicators = [];
|
||||
foreach ($modeldata->indicators as $indicatorclass) {
|
||||
$indicator = \core_analytics\manager::get_indicator($indicatorclass);
|
||||
$indicators[$indicator->get_id()] = $indicator;
|
||||
}
|
||||
$model = \core_analytics\model::create($target, $indicators, $modeldata->timesplitting, $modeldata->processor);
|
||||
|
||||
// Import them disabled.
|
||||
$model->update(false, false, false, false);
|
||||
|
||||
if ($mlbackenddir) {
|
||||
$modeldir = $model->get_output_dir(['execution']);
|
||||
if (!$model->get_predictions_processor(true)->import($model->get_unique_id(), $modeldir, $mlbackenddir)) {
|
||||
throw new \moodle_exception('errorimport', 'analytics');
|
||||
}
|
||||
$model->mark_as_trained();
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the provided model configuration can be deployed in this site.
|
||||
*
|
||||
* @param \stdClass $modeldata
|
||||
* @param bool $ignoreversionmismatches
|
||||
* @return string|null Error string or null if all good.
|
||||
*/
|
||||
public function check_dependencies(\stdClass $modeldata, bool $ignoreversionmismatches) : ?string {
|
||||
|
||||
$siteversions = \core_component::get_all_versions();
|
||||
|
||||
// Possible issues.
|
||||
$missingcomponents = [];
|
||||
$versionmismatches = [];
|
||||
$missingclasses = [];
|
||||
|
||||
// We first check that this site has the required dependencies and the required versions.
|
||||
foreach ($modeldata->dependencies as $component => $importversion) {
|
||||
|
||||
if (empty($siteversions[$component])) {
|
||||
|
||||
if ($component === 'core') {
|
||||
$component = 'Moodle';
|
||||
}
|
||||
$missingcomponents[$component] = $component . ' (' . $importversion . ')';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($siteversions[$component] == $importversion) {
|
||||
// All good here.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$ignoreversionmismatches) {
|
||||
if ($component === 'core') {
|
||||
$component = 'Moodle';
|
||||
}
|
||||
$versionmismatches[$component] = $component . ' (' . $importversion . ')';
|
||||
}
|
||||
}
|
||||
|
||||
// Checking that each of the components is available.
|
||||
if (!$target = manager::get_target($modeldata->target)) {
|
||||
$missingclasses[] = $modeldata->target;
|
||||
}
|
||||
|
||||
if (!$timesplitting = manager::get_time_splitting($modeldata->timesplitting)) {
|
||||
$missingclasses[] = $modeldata->timesplitting;
|
||||
}
|
||||
|
||||
// Indicators.
|
||||
foreach ($modeldata->indicators as $indicatorclass) {
|
||||
if (!$indicator = manager::get_indicator($indicatorclass)) {
|
||||
$missingclasses[] = $indicatorclass;
|
||||
}
|
||||
}
|
||||
|
||||
// ML backend.
|
||||
if (!empty($modeldata->processor)) {
|
||||
if (!$processor = \core_analytics\manager::get_predictions_processor($modeldata->processor, false)) {
|
||||
$missingclasses[] = $indicatorclass;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($missingcomponents)) {
|
||||
return get_string('errorimportmissingcomponents', 'analytics', join(', ', $missingcomponents));
|
||||
}
|
||||
|
||||
if (!empty($versionmismatches)) {
|
||||
return get_string('errorimportversionmismatches', 'analytics', implode(', ', $versionmismatches));
|
||||
}
|
||||
|
||||
if (!empty($missingclasses)) {
|
||||
$a = (object)[
|
||||
'missingclasses' => implode(', ', $missingclasses),
|
||||
];
|
||||
return get_string('errorimportmissingclasses', 'analytics', $a);
|
||||
}
|
||||
|
||||
// No issues found.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component the class belongs to.
|
||||
*
|
||||
* Note that this method does not work for global space classes.
|
||||
*
|
||||
* @param string $fullclassname Qualified name including the namespace.
|
||||
* @return string|null Frankenstyle component
|
||||
*/
|
||||
public static function get_class_component(string $fullclassname) : ?string {
|
||||
|
||||
// Strip out leading backslash.
|
||||
$fullclassname = ltrim($fullclassname, '\\');
|
||||
|
||||
$nextbackslash = strpos($fullclassname, '\\');
|
||||
if ($nextbackslash === false) {
|
||||
// Global space.
|
||||
return 'core';
|
||||
}
|
||||
$component = substr($fullclassname, 0, $nextbackslash);
|
||||
|
||||
// All core subsystems use core's version.php.
|
||||
if (strpos($component, 'core_') === 0) {
|
||||
$component = 'core';
|
||||
}
|
||||
|
||||
return $component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the import zip contents.
|
||||
*
|
||||
* @param string $zipfilepath Zip file path
|
||||
* @return array [0] => \stdClass, [1] => string
|
||||
*/
|
||||
public function extract_import_contents(string $zipfilepath) : array {
|
||||
|
||||
$importtempdir = make_request_directory('analyticsimport' . microtime(false));
|
||||
|
||||
$zip = new \zip_packer();
|
||||
$filelist = $zip->extract_to_pathname($zipfilepath, $importtempdir);
|
||||
|
||||
if (empty($filelist[self::CONFIG_FILE_NAME])) {
|
||||
// Missing required file.
|
||||
throw new \moodle_exception('errorimport', 'analytics');
|
||||
}
|
||||
|
||||
$jsonmodeldata = file_get_contents($importtempdir . DIRECTORY_SEPARATOR . self::CONFIG_FILE_NAME);
|
||||
|
||||
if (!$modeldata = json_decode($jsonmodeldata)) {
|
||||
throw new \moodle_exception('errorimport', 'analytics');
|
||||
}
|
||||
|
||||
if (empty($modeldata->target) || empty($modeldata->timesplitting) || empty($modeldata->indicators)) {
|
||||
throw new \moodle_exception('errorimport', 'analytics');
|
||||
}
|
||||
|
||||
$mlbackenddir = $importtempdir . DIRECTORY_SEPARATOR . 'mlbackend';
|
||||
if (!is_dir($mlbackenddir)) {
|
||||
$mlbackenddir = false;
|
||||
}
|
||||
|
||||
return [$modeldata, $mlbackenddir];
|
||||
}
|
||||
/**
|
||||
* Exports the configuration of the model.
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function export_model_data() : \stdClass {
|
||||
|
||||
$versions = \core_component::get_all_versions();
|
||||
|
||||
$data = new \stdClass();
|
||||
|
||||
// Target.
|
||||
$data->target = $this->model->get_target()->get_id();
|
||||
$requiredclasses[] = $data->target;
|
||||
|
||||
// Time splitting method.
|
||||
$data->timesplitting = $this->model->get_time_splitting()->get_id();
|
||||
$requiredclasses[] = $data->timesplitting;
|
||||
|
||||
// Model indicators.
|
||||
$data->indicators = [];
|
||||
foreach ($this->model->get_indicators() as $indicator) {
|
||||
$indicatorid = $indicator->get_id();
|
||||
$data->indicators[] = $indicatorid;
|
||||
$requiredclasses[] = $indicatorid;
|
||||
}
|
||||
|
||||
// Return the predictions processor this model is using, even if no predictions processor
|
||||
// was explicitly selected.
|
||||
$predictionsprocessor = $this->model->get_predictions_processor();
|
||||
$data->processor = '\\' . get_class($predictionsprocessor);
|
||||
$requiredclasses[] = $data->processor;
|
||||
|
||||
// Add information for versioning.
|
||||
$data->dependencies = [];
|
||||
foreach ($requiredclasses as $fullclassname) {
|
||||
$component = $this->get_class_component($fullclassname);
|
||||
$data->dependencies[$component] = $versions[$component];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Exportable machine learning backend interface.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_analytics;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Exportable machine learning backend interface.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
interface packable {
|
||||
|
||||
/**
|
||||
* Exports the machine learning model.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that contains the trained model.
|
||||
* @return string The path to the directory that contains the exported model.
|
||||
*/
|
||||
public function export(string $uniqueid, string $modeldir) : string;
|
||||
|
||||
/**
|
||||
* Imports the provided machine learning model.
|
||||
*
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that will contain the trained model.
|
||||
* @param string $importdir The directory that contains the files to import.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function import(string $uniqueid, string $modeldir, string $importdir) : bool;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ require_once(__DIR__ . '/fixtures/test_indicator_max.php');
|
||||
require_once(__DIR__ . '/fixtures/test_indicator_min.php');
|
||||
require_once(__DIR__ . '/fixtures/test_indicator_fullname.php');
|
||||
require_once(__DIR__ . '/fixtures/test_target_shortname.php');
|
||||
require_once(__DIR__ . '/fixtures/test_static_target_shortname.php');
|
||||
require_once(__DIR__ . '/fixtures/test_target_course_level_shortname.php');
|
||||
require_once(__DIR__ . '/fixtures/test_analyser.php');
|
||||
|
||||
@@ -317,6 +318,95 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
$this->assertLessThanOrEqual(2, $DB->count_records('analytics_used_analysables', $params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test model_config::get_class_component.
|
||||
*/
|
||||
public function test_model_config_get_class_component() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$this->assertEquals('core',
|
||||
\core_analytics\model_config::get_class_component('\\core\\analytics\\indicator\\read_actions'));
|
||||
$this->assertEquals('core',
|
||||
\core_analytics\model_config::get_class_component('core\\analytics\\indicator\\read_actions'));
|
||||
$this->assertEquals('core',
|
||||
\core_analytics\model_config::get_class_component('\\core_course\\analytics\\indicator\\completion_enabled'));
|
||||
$this->assertEquals('mod_forum',
|
||||
\core_analytics\model_config::get_class_component('\\mod_forum\\analytics\\indicator\\cognitive_depth'));
|
||||
|
||||
$this->assertEquals('core', \core_analytics\model_config::get_class_component('\\core_class'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that import_model import models' configurations.
|
||||
*/
|
||||
public function test_import_model_config() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$this->model->enable('\\core\\analytics\\time_splitting\\quarters');
|
||||
$zipfilepath = $this->model->export_model('yeah-config.zip');
|
||||
|
||||
$this->modelobj = $this->model->get_model_obj();
|
||||
|
||||
$importedmodelobj = \core_analytics\model::import_model($zipfilepath)->get_model_obj();
|
||||
|
||||
$this->assertSame($this->modelobj->target, $importedmodelobj->target);
|
||||
$this->assertSame($this->modelobj->indicators, $importedmodelobj->indicators);
|
||||
$this->assertSame($this->modelobj->timesplitting, $importedmodelobj->timesplitting);
|
||||
|
||||
$predictionsprocessor = $this->model->get_predictions_processor();
|
||||
$this->assertSame('\\' . get_class($predictionsprocessor), $importedmodelobj->predictionsprocessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can export configuration
|
||||
*/
|
||||
public function test_can_export_configuration() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// No time splitting method.
|
||||
$this->assertFalse($this->model->can_export_configuration());
|
||||
|
||||
$this->model->enable('\\core\\analytics\\time_splitting\\quarters');
|
||||
$this->assertTrue($this->model->can_export_configuration());
|
||||
|
||||
$this->model->update(true, [], false);
|
||||
$this->assertFalse($this->model->can_export_configuration());
|
||||
|
||||
$statictarget = new test_static_target_shortname();
|
||||
$indicators['test_indicator_max'] = \core_analytics\manager::get_indicator('test_indicator_max');
|
||||
$model = \core_analytics\model::create($statictarget, $indicators, '\\core\\analytics\\time_splitting\\quarters');
|
||||
$this->assertFalse($model->can_export_configuration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test export_config
|
||||
*/
|
||||
public function test_export_config() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$this->model->enable('\\core\\analytics\\time_splitting\\quarters');
|
||||
|
||||
$modelconfig = new \core_analytics\model_config($this->model);
|
||||
|
||||
$method = new ReflectionMethod('\\core_analytics\\model_config', 'export_model_data');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$modeldata = $method->invoke($modelconfig);
|
||||
|
||||
$this->assertArrayHasKey('core', $modeldata->dependencies);
|
||||
$this->assertInternalType('float', $modeldata->dependencies['core']);
|
||||
$this->assertNotEmpty($modeldata->target);
|
||||
$this->assertNotEmpty($modeldata->timesplitting);
|
||||
$this->assertCount(3, $modeldata->indicators);
|
||||
|
||||
$indicators['test_indicator_max'] = \core_analytics\manager::get_indicator('test_indicator_max');
|
||||
$this->model->update(true, $indicators, false);
|
||||
|
||||
$modeldata = $method->invoke($modelconfig);
|
||||
|
||||
$this->assertCount(1, $modeldata->indicators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a model log record.
|
||||
*/
|
||||
@@ -346,17 +436,6 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
*/
|
||||
class testable_model extends \core_analytics\model {
|
||||
|
||||
/**
|
||||
* get_output_dir
|
||||
*
|
||||
* @param array $subdirs
|
||||
* @param bool $onlymodelid
|
||||
* @return string
|
||||
*/
|
||||
public function get_output_dir($subdirs = array(), $onlymodelid = false) {
|
||||
return parent::get_output_dir($subdirs, $onlymodelid);
|
||||
}
|
||||
|
||||
/**
|
||||
* init_analyser
|
||||
*
|
||||
|
||||
@@ -119,23 +119,9 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$this->setAdminuser();
|
||||
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
||||
|
||||
$ncourses = 10;
|
||||
|
||||
// Generate training data.
|
||||
$params = array(
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
'enddate' => mktime(0, 0, 0, 2, 24, 2016),
|
||||
);
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'a' . random_string(10);
|
||||
$courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($courseparams);
|
||||
}
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'b' . random_string(10);
|
||||
$courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($courseparams);
|
||||
}
|
||||
$ncourses = 10;
|
||||
$this->generate_courses($ncourses);
|
||||
|
||||
// We repeat the test for all prediction processors.
|
||||
$predictionsprocessor = \core_analytics\manager::get_predictions_processor($predictionsprocessorclass, false);
|
||||
@@ -171,6 +157,10 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$this->assertEmpty($fs->get_directory_files(\context_system::instance()->id, 'analytics',
|
||||
\core_analytics\dataset_manager::UNLABELLED_FILEAREA, $model->get_id(), '/analysable/', true, false));
|
||||
|
||||
$params = [
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
'enddate' => mktime(0, 0, 0, 2, 24, 2016),
|
||||
];
|
||||
$courseparams = $params + array('shortname' => 'aaaaaa', 'fullname' => 'aaaaaa', 'visible' => 0);
|
||||
$course1 = $this->getDataGenerator()->create_course($courseparams);
|
||||
$courseparams = $params + array('shortname' => 'bbbbbb', 'fullname' => 'bbbbbb', 'visible' => 0);
|
||||
@@ -280,6 +270,67 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
return $this->add_prediction_processors($cases);
|
||||
}
|
||||
|
||||
/**
|
||||
* test_ml_export_import
|
||||
*
|
||||
* @param string $predictionsprocessorclass The class name
|
||||
* @dataProvider provider_ml_export_import
|
||||
*/
|
||||
public function test_ml_export_import($predictionsprocessorclass) {
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminuser();
|
||||
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
||||
|
||||
// Generate training data.
|
||||
$ncourses = 10;
|
||||
$this->generate_courses($ncourses);
|
||||
|
||||
// We repeat the test for all prediction processors.
|
||||
$predictionsprocessor = \core_analytics\manager::get_predictions_processor($predictionsprocessorclass, false);
|
||||
if ($predictionsprocessor->is_ready() !== true) {
|
||||
$this->markTestSkipped('Skipping ' . $predictionsprocessorclass . ' as the predictor is not ready.');
|
||||
}
|
||||
|
||||
$model = $this->add_perfect_model();
|
||||
$model->update(true, false, '\core\analytics\time_splitting\quarters', get_class($predictionsprocessor));
|
||||
|
||||
$model->train();
|
||||
|
||||
$this->generate_courses(10, ['visible' => 0]);
|
||||
|
||||
$originalresults = $model->predict();
|
||||
|
||||
$zipfilename = 'model-zip-' . microtime() . '.zip';
|
||||
$zipfilepath = $model->export_model($zipfilename);
|
||||
|
||||
$importmodel = \core_analytics\model::import_model($zipfilepath);
|
||||
$importmodel->enable();
|
||||
|
||||
// Now predict using the imported model without prior training.
|
||||
$importedmodelresults = $importmodel->predict();
|
||||
|
||||
foreach ($originalresults->predictions as $sampleid => $prediction) {
|
||||
$this->assertEquals($importedmodelresults->predictions[$sampleid]->prediction, $prediction->prediction);
|
||||
}
|
||||
|
||||
set_config('enabled_stores', '', 'tool_log');
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* provider_ml_export_import
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provider_ml_export_import() {
|
||||
$cases = [
|
||||
'case' => [],
|
||||
];
|
||||
|
||||
// We need to test all system prediction processors.
|
||||
return $this->add_prediction_processors($cases);
|
||||
}
|
||||
/**
|
||||
* Test the system classifiers returns.
|
||||
*
|
||||
@@ -400,20 +451,7 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
// Generate training data.
|
||||
$params = array(
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
'enddate' => mktime(0, 0, 0, 2, 24, 2016),
|
||||
);
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'a' . random_string(10);
|
||||
$params = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($params);
|
||||
}
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'b' . random_string(10);
|
||||
$params = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($params);
|
||||
}
|
||||
$this->generate_courses($ncourses);
|
||||
|
||||
// We repeat the test for all prediction processors.
|
||||
$predictionsprocessor = \core_analytics\manager::get_predictions_processor($predictionsprocessorclass, false);
|
||||
@@ -579,6 +617,32 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
return new \core_analytics\model($model->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates $ncourses courses
|
||||
*
|
||||
* @param int $ncourses The number of courses to be generated.
|
||||
* @param array $params Course params
|
||||
* @return null
|
||||
*/
|
||||
protected function generate_courses($ncourses, array $params = []) {
|
||||
|
||||
$params = $params + [
|
||||
'startdate' => mktime(0, 0, 0, 10, 24, 2015),
|
||||
'enddate' => mktime(0, 0, 0, 2, 24, 2016),
|
||||
];
|
||||
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'a' . random_string(10);
|
||||
$courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($courseparams);
|
||||
}
|
||||
for ($i = 0; $i < $ncourses; $i++) {
|
||||
$name = 'b' . random_string(10);
|
||||
$courseparams = array('shortname' => $name, 'fullname' => $name) + $params;
|
||||
$this->getDataGenerator()->create_course($courseparams);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add_prediction_processors
|
||||
*
|
||||
|
||||
@@ -38,8 +38,16 @@ $string['erroralreadypredict'] = 'File {$a} has already been used to generate pr
|
||||
$string['errorcannotreaddataset'] = 'Dataset file {$a} can not be read';
|
||||
$string['errorcannotwritedataset'] = 'Dataset file {$a} cannot be written';
|
||||
$string['errorendbeforestart'] = 'The end date ({$a}) is before the course start date.';
|
||||
$string['errorexportmodelresult'] = 'The machine learning model can not be exported.';
|
||||
$string['errorimport'] = 'Error importing the provided json file.';
|
||||
$string['errorimportmissingcomponents'] = 'The provided model requires the following plugins to be installed: {$a}. Note that the versions do not necessarily need to match with the versions installed in your system. To install the same or a newer version of the plugin should be enough in most cases.';
|
||||
$string['errorimportversionmismatches'] = 'The version of the following components differ from the version installed in this site: {$a}. You can use "Ignore version mismatches" option to ignore these differences.';
|
||||
$string['errorimportmissingclasses'] = 'The following analytics components are not available in this site: {$a->missingclasses}. ';
|
||||
$string['errorinvalidindicator'] = 'Invalid {$a} indicator';
|
||||
$string['errorinvalidtarget'] = 'Invalid {$a} target';
|
||||
$string['errorinvalidtimesplitting'] = 'Invalid time splitting; please ensure you add the class fully qualified class name.';
|
||||
$string['errornoexportconfig'] = 'There was a problem exporting the model configuration.';
|
||||
$string['errornoexportconfigrequirements'] = 'Only non static models with timeplitting methods can be exported.';
|
||||
$string['errornoindicators'] = 'This model does not have any indicators.';
|
||||
$string['errornopredictresults'] = 'No results returned from the predictions processor. Check the output directory contents for more information.';
|
||||
$string['errornotimesplittings'] = 'This model does not have any time-splitting method.';
|
||||
|
||||
@@ -1141,6 +1141,17 @@ $cache = '.var_export($cache, true).';
|
||||
* @return string sha1 hash
|
||||
*/
|
||||
public static function get_all_versions_hash() {
|
||||
return sha1(serialize(self::get_all_versions()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hash of all versions including core and all plugins.
|
||||
*
|
||||
* This is relatively slow and not fully cached, use with care!
|
||||
*
|
||||
* @return array as (string)plugintype_pluginname => (int)version
|
||||
*/
|
||||
public static function get_all_versions() : array {
|
||||
global $CFG;
|
||||
|
||||
self::init();
|
||||
@@ -1174,7 +1185,7 @@ $cache = '.var_export($cache, true).';
|
||||
}
|
||||
}
|
||||
|
||||
return sha1(serialize($versions));
|
||||
return $versions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ use Phpml\ModelManager;
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class processor implements \core_analytics\classifier, \core_analytics\regressor {
|
||||
class processor implements \core_analytics\classifier, \core_analytics\regressor, \core_analytics\packable {
|
||||
|
||||
/**
|
||||
* Size of training / prediction batches.
|
||||
@@ -103,8 +103,7 @@ class processor implements \core_analytics\classifier, \core_analytics\regressor
|
||||
*/
|
||||
public function train_classification($uniqueid, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Output directory is already unique to the model.
|
||||
$modelfilepath = $outputdir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME;
|
||||
$modelfilepath = $this->get_model_filepath($outputdir);
|
||||
|
||||
$modelmanager = new ModelManager();
|
||||
|
||||
@@ -175,8 +174,7 @@ class processor implements \core_analytics\classifier, \core_analytics\regressor
|
||||
*/
|
||||
public function classify($uniqueid, \stored_file $dataset, $outputdir) {
|
||||
|
||||
// Output directory is already unique to the model.
|
||||
$modelfilepath = $outputdir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME;
|
||||
$modelfilepath = $this->get_model_filepath($outputdir);
|
||||
|
||||
if (!file_exists($modelfilepath)) {
|
||||
throw new \moodle_exception('errorcantloadmodel', 'mlbackend_php', '', $modelfilepath);
|
||||
@@ -424,6 +422,77 @@ class processor implements \core_analytics\classifier, \core_analytics\regressor
|
||||
throw new \coding_exception('This predictor does not support regression yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the machine learning model.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that contains the trained model.
|
||||
* @return string The path to the directory that contains the exported model.
|
||||
*/
|
||||
public function export(string $uniqueid, string $modeldir) : string {
|
||||
|
||||
$modelfilepath = $this->get_model_filepath($modeldir);
|
||||
|
||||
if (!file_exists($modelfilepath)) {
|
||||
throw new \moodle_exception('errorexportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
// We can use the actual $modeldir as the directory is not modified during export, just copied into a zip.
|
||||
return $modeldir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the provided machine learning model.
|
||||
*
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that will contain the trained model.
|
||||
* @param string $importdir The directory that contains the files to import.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function import(string $uniqueid, string $modeldir, string $importdir) : bool {
|
||||
|
||||
$importmodelfilepath = $this->get_model_filepath($importdir);
|
||||
$modelfilepath = $this->get_model_filepath($modeldir);
|
||||
|
||||
$modelmanager = new ModelManager();
|
||||
|
||||
// Copied from ModelManager::restoreFromFile to validate the serialised contents
|
||||
// before restoring them.
|
||||
$importconfig = file_get_contents($importmodelfilepath);
|
||||
|
||||
// Clean stuff like function calls.
|
||||
$importconfig = preg_replace('/[^a-zA-Z0-9\{\}%\.\*\;\,\:\"\-\0\\\]/', '', $importconfig);
|
||||
|
||||
$object = unserialize($importconfig,
|
||||
['allowed_classes' => ['Phpml\\Classification\\Linear\\LogisticRegression']]);
|
||||
if (!$object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_class($object) == '__PHP_Incomplete_Class') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$classifier = $modelmanager->restoreFromFile($importmodelfilepath);
|
||||
|
||||
// This would override any previous classifier.
|
||||
$modelmanager->saveToFile($classifier, $modelfilepath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the serialised model file in the provided directory.
|
||||
*
|
||||
* @param string $modeldir The model directory
|
||||
* @return string The model file
|
||||
*/
|
||||
protected function get_model_filepath(string $modeldir) : string {
|
||||
// Output directory is already unique to the model.
|
||||
return $modeldir . DIRECTORY_SEPARATOR . self::MODEL_FILENAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Phi correlation coefficient.
|
||||
*
|
||||
|
||||
@@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class processor implements \core_analytics\classifier, \core_analytics\regressor {
|
||||
class processor implements \core_analytics\classifier, \core_analytics\regressor, \core_analytics\packable {
|
||||
|
||||
/**
|
||||
* The required version of the python package that performs all calculations.
|
||||
@@ -263,6 +263,78 @@ class processor implements \core_analytics\classifier, \core_analytics\regresso
|
||||
return $resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the machine learning model.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that contains the trained model.
|
||||
* @return string The path to the directory that contains the exported model.
|
||||
*/
|
||||
public function export(string $uniqueid, string $modeldir) : string {
|
||||
|
||||
// We include an exporttmpdir as we want to be sure that the file is not deleted after the
|
||||
// python process finishes.
|
||||
$exporttmpdir = make_request_directory('mlbackend_python_export');
|
||||
|
||||
$cmd = "{$this->pathtopython} -m moodlemlbackend.export " .
|
||||
escapeshellarg($uniqueid) . ' ' .
|
||||
escapeshellarg($modeldir) . ' ' .
|
||||
escapeshellarg($exporttmpdir);
|
||||
|
||||
if (!PHPUNIT_TEST && CLI_SCRIPT) {
|
||||
debugging($cmd, DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$output = null;
|
||||
$exitcode = null;
|
||||
$exportdir = exec($cmd, $output, $exitcode);
|
||||
|
||||
if ($exitcode != 0) {
|
||||
throw new \moodle_exception('errorexportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
if (!$exportdir) {
|
||||
throw new \moodle_exception('errorexportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
return $exportdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the provided machine learning model.
|
||||
*
|
||||
* @param string $uniqueid The model unique id
|
||||
* @param string $modeldir The directory that will contain the trained model.
|
||||
* @param string $importdir The directory that contains the files to import.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function import(string $uniqueid, string $modeldir, string $importdir) : bool {
|
||||
|
||||
$cmd = "{$this->pathtopython} -m moodlemlbackend.import " .
|
||||
escapeshellarg($uniqueid) . ' ' .
|
||||
escapeshellarg($modeldir) . ' ' .
|
||||
escapeshellarg($importdir);
|
||||
|
||||
if (!PHPUNIT_TEST && CLI_SCRIPT) {
|
||||
debugging($cmd, DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
$output = null;
|
||||
$exitcode = null;
|
||||
$success = exec($cmd, $output, $exitcode);
|
||||
|
||||
if ($exitcode != 0) {
|
||||
throw new \moodle_exception('errorimportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
throw new \moodle_exception('errorimportmodelresult', 'analytics');
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Train this processor regression model using the provided supervised learning dataset.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user