MDL-65175 tool_analytics: Select a time-splitting for evaluation

This commit is contained in:
David Monllaó 2019-04-04 15:53:05 +02:00
parent dafcc3cf7b
commit 84bc827932
8 changed files with 138 additions and 59 deletions

View File

@ -1 +1 @@
define(["jquery","core/str","core/log","core/notification","core/modal_factory","core/modal_events","core/templates"],function(a,b,c,d,e,f,g){var h={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"}}},i=function(b){return a(b.closest("tr")[0]).find("span.target-name").text()};return{confirmAction:function(g,j){a('[data-action-id="'+g+'"]').on("click",function(g){g.preventDefault();var k=a(g.currentTarget);if("undefined"==typeof h[j])return void c.error('Action "'+j+'" is not allowed.');var l=[h[j].title,h[j].body];l[1].param=i(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)})},selectEvaluationMode:function(c,h){a('[data-action-id="'+c+'"]').on("click",function(c){c.preventDefault();var i=a(c.currentTarget);if(!h)return void(window.location.href=i.attr("href"));var j=b.get_strings([{key:"evaluatemodel",component:"tool_analytics"},{key:"evaluationmode",component:"tool_analytics"}]),k=e.create({type:e.types.SAVE_CANCEL}),l=g.render("tool_analytics/evaluation_mode_selection",{});a.when(j,k).then(function(b,c){return c.getRoot().on(f.hidden,c.destroy.bind(c)),c.setTitle(b[1]),c.setSaveButtonText(b[0]),c.setBody(l),c.getRoot().on(f.save,function(){var b=a("input[name='evaluationmode']:checked").val();"trainedmodel"==b&&i.attr("href",i.attr("href")+"&mode=trainedmodel"),window.location.href=i.attr("href")}),c.show(),c}).fail(d.exception)})},selectExportOptions:function(c,h){a('[data-action-id="'+c+'"]').on("click",function(c){c.preventDefault();var i=a(c.currentTarget);if(!h)return i.attr("href",i.attr("href")+"&action=exportmodel&includeweights=0"),void(window.location.href=i.attr("href"));var j=b.get_strings([{key:"export",component:"tool_analytics"}]),k=e.create({type:e.types.SAVE_CANCEL}),l=g.render("tool_analytics/export_options",{});a.when(j,k).then(function(b,c){return c.getRoot().on(f.hidden,c.destroy.bind(c)),c.setTitle(b[0]),c.setSaveButtonText(b[0]),c.setBody(l),c.getRoot().on(f.save,function(){var b=a("input[name='exportoption']:checked").val();"exportdata"==b?i.attr("href",i.attr("href")+"&action=exportdata"):(i.attr("href",i.attr("href")+"&action=exportmodel"),a("#id-includeweights").is(":checked")?i.attr("href",i.attr("href")+"&includeweights=1"):i.attr("href",i.attr("href")+"&includeweights=0")),window.location.href=i.attr("href")}),c.show(),c}).fail(d.exception)})}}});
define(["jquery","core/str","core/log","core/notification","core/modal_factory","core/modal_events","core/templates"],function(a,b,c,d,e,f,g){var h={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"}}},i=function(b){return a(b.closest("tr")[0]).find("span.target-name").text()};return{confirmAction:function(g,j){a('[data-action-id="'+g+'"]').on("click",function(g){g.preventDefault();var k=a(g.currentTarget);if("undefined"==typeof h[j])return void c.error('Action "'+j+'" is not allowed.');var l=[h[j].title,h[j].body];l[1].param=i(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)})},selectEvaluationOptions:function(c,h,i){a('[data-action-id="'+c+'"]').on("click",function(c){c.preventDefault();var j=a(c.currentTarget),k=b.get_strings([{key:"evaluatemodel",component:"tool_analytics"},{key:"evaluate",component:"tool_analytics"}]),l=e.create({type:e.types.SAVE_CANCEL}),m=g.render("tool_analytics/evaluation_options",{trainedexternally:h,timesplittingmethods:i});a.when(k,l).then(function(b,c){return c.getRoot().on(f.hidden,c.destroy.bind(c)),c.setTitle(b[0]),c.setSaveButtonText(b[1]),c.setBody(m),c.getRoot().on(f.save,function(){var b=a("input[name='evaluationmode']:checked").val();"trainedmodel"==b&&j.attr("href",j.attr("href")+"&mode=trainedmodel");var c=a("#id-evaluation-timesplitting").val();j.attr("href",j.attr("href")+"&timesplitting="+c),window.location.href=j.attr("href")}),c.show(),c}).fail(d.exception)})},selectExportOptions:function(c,h){a('[data-action-id="'+c+'"]').on("click",function(c){c.preventDefault();var i=a(c.currentTarget);if(!h)return i.attr("href",i.attr("href")+"&action=exportmodel&includeweights=0"),void(window.location.href=i.attr("href"));var j=b.get_strings([{key:"export",component:"tool_analytics"}]),k=e.create({type:e.types.SAVE_CANCEL}),l=g.render("tool_analytics/export_options",{});a.when(j,k).then(function(b,c){return c.getRoot().on(f.hidden,c.destroy.bind(c)),c.setTitle(b[0]),c.setSaveButtonText(b[0]),c.setBody(l),c.getRoot().on(f.save,function(){var b=a("input[name='exportoption']:checked").val();"exportdata"==b?i.attr("href",i.attr("href")+"&action=exportdata"):(i.attr("href",i.attr("href")+"&action=exportmodel"),a("#id-includeweights").is(":checked")?i.attr("href",i.attr("href")+"&includeweights=1"):i.attr("href",i.attr("href")+"&includeweights=0")),window.location.href=i.attr("href")}),c.show(),c}).fail(d.exception)})}}});

View File

@ -101,50 +101,53 @@ define(['jquery', 'core/str', 'core/log', 'core/notification', 'core/modal_facto
},
/**
* Displays a select-evaluation-mode choice.
* Displays evaluation mode and time-splitting method choices.
*
* @param {String} actionId
* @param {Boolean} trainedOnlyExternally
*/
selectEvaluationMode: function(actionId, trainedOnlyExternally) {
selectEvaluationOptions: function(actionId, trainedOnlyExternally, timeSplittingMethods) {
$('[data-action-id="' + actionId + '"]').on('click', function(ev) {
ev.preventDefault();
var a = $(ev.currentTarget);
if (!trainedOnlyExternally) {
// We can not evaluate trained models if the model was trained using data from this site.
// Default to evaluate the model configuration if that is the case.
window.location.href = a.attr('href');
return;
}
var stringsPromise = Str.get_strings([
{
key: 'evaluatemodel',
component: 'tool_analytics'
}, {
key: 'evaluationmode',
key: 'evaluate',
component: 'tool_analytics'
}
]);
var modalPromise = ModalFactory.create({type: ModalFactory.types.SAVE_CANCEL});
var bodyPromise = Templates.render('tool_analytics/evaluation_mode_selection', {});
var bodyPromise = Templates.render('tool_analytics/evaluation_options', {
trainedexternally: trainedOnlyExternally,
timesplittingmethods: timeSplittingMethods
});
$.when(stringsPromise, modalPromise).then(function(strings, modal) {
modal.getRoot().on(ModalEvents.hidden, modal.destroy.bind(modal));
modal.setTitle(strings[1]);
modal.setSaveButtonText(strings[0]);
modal.setTitle(strings[0]);
modal.setSaveButtonText(strings[1]);
modal.setBody(bodyPromise);
modal.getRoot().on(ModalEvents.save, function() {
// Evaluation mode.
var evaluationMode = $("input[name='evaluationmode']:checked").val();
if (evaluationMode == 'trainedmodel') {
a.attr('href', a.attr('href') + '&mode=trainedmodel');
}
// Selected time-splitting id.
var timeSplittingMethod = $("#id-evaluation-timesplitting").val();
a.attr('href', a.attr('href') + '&timesplitting=' + timeSplittingMethod);
window.location.href = a.attr('href');
return;
});

View File

@ -45,7 +45,7 @@ class helper {
// 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
// to ever be part of a class name.
return str_replace('\\', '2015102400ouuu', $class);
return str_replace('\\', '__', $class);
}
/**
@ -56,7 +56,7 @@ class helper {
*/
public static function option_to_class($option) {
// Really unlikely but yeah, I'm a bad booyyy.
return str_replace('2015102400ouuu', '\\', $option);
return str_replace('__', '\\', $option);
}
/**

View File

@ -71,6 +71,17 @@ class models_list implements \renderable, \templatable {
$onlycli = 1;
}
// Evaluation options.
$timesplittingmethods = [
['id' => 'all', 'text' => get_string('alltimesplittingmethods', 'tool_analytics')],
];
foreach (\core_analytics\manager::get_all_time_splittings() as $timesplitting) {
$timesplittingmethods[] = [
'id' => \tool_analytics\output\helper::class_to_option($timesplitting->get_id()),
'text' => $timesplitting->get_name()->out(),
];
}
$data->models = array();
foreach ($this->models as $model) {
$modeldata = $model->export();
@ -192,7 +203,16 @@ class models_list implements \renderable, \templatable {
$trainedonlyexternally = !$model->trained_locally() && $model->is_trained();
$actionid = 'evaluate-' . $model->get_id();
$PAGE->requires->js_call_amd('tool_analytics/model', 'selectEvaluationMode', [$actionid, $trainedonlyexternally]);
// Include the current time-splitting method as the default selection method the model already have one.
$modeltimesplittingmethods = $timesplittingmethods;
if ($model->get_model_obj()->timesplitting) {
$currenttimesplitting = ['id' => 'current', 'text' => get_string('currenttimesplitting', 'tool_analytics')];
array_unshift($modeltimesplittingmethods, $currenttimesplitting);
}
$evaluateparams = [$actionid, $trainedonlyexternally, $modeltimesplittingmethods];
$PAGE->requires->js_call_amd('tool_analytics/model', 'selectEvaluationOptions', $evaluateparams);
$urlparams['action'] = 'evaluate';
$url = new \moodle_url('model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/calc', get_string('evaluate', 'tool_analytics')),

View File

@ -24,6 +24,7 @@
$string['accuracy'] = 'Accuracy';
$string['allpredictions'] = 'All predictions';
$string['alltimesplittingmethods'] = 'All time-splitting methods';
$string['analysingsitedata'] = 'Analysing the site';
$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.';
@ -36,6 +37,7 @@ $string['clienablemodel'] = 'You can enable the model by selecting a time-splitt
$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['currenttimesplitting'] = 'Current time-splitting method';
$string['delete'] = 'Delete';
$string['deletemodelconfirmation'] = 'Are you sure you want to delete "{$a}"? These changes can not be reverted.';
$string['disabled'] = 'Disabled';
@ -111,6 +113,7 @@ $string['predictionprocessfinished'] = 'Prediction process finished';
$string['previouspage'] = 'Previous page';
$string['samestartdate'] = 'Current start date is good';
$string['sameenddate'] = 'Current end date is good';
$string['selecttimesplittingforevaluation'] = 'Select the time-splitting method you want to use to evaluate the model configuration.';
$string['target'] = 'Target';
$string['target_help'] = 'The target is what the model will predict.';
$string['target_link'] = 'Targets';

View File

@ -172,7 +172,19 @@ switch ($action) {
$mode = optional_param('mode', false, PARAM_ALPHANUM);
if ($mode == 'trainedmodel') {
$options['mode'] = 'trainedmodel';
} else {
// All is the default in core_analytics\model::evaluate() as well.
$timesplitting = optional_param('timesplitting', 'all', PARAM_ALPHANUMEXT);
if ($timesplitting === 'current') {
$options['timesplitting'] = \core_analytics\manager::get_time_splitting($model->get_model_obj()->timesplitting);
} else if ($timesplitting !== 'all' && $timesplitting !== 'current') {
$options['timesplitting'] = \core_analytics\manager::get_time_splitting(
\tool_analytics\output\helper::option_to_class($timesplitting)
);
}
}
$results = $model->evaluate($options);
// We reset the theme and the output as some indicators may be using external functions

View File

@ -1,42 +0,0 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_analytics/evaluation_mode_selector
Evaluation mode selector.
The purpose of this template is to render the evaluation mode radio button.
Classes required for JS:
* none
Data attributes required for JS:
* none
Example context (json):
{
}
}}
<div class="box mb-4">{{#str}} evaluationmodeinfo, tool_analytics {{/str}}</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="evaluationmode" id="id-mode-trainedmodel" value="trainedmodel" checked>
<label class="form-check-label" for="id-mode-trainedmodel">{{#str}} evaluationmodetrainedmodel, tool_analytics {{/str}}</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="evaluationmode" id="id-mode-configuration" value="configuration">
<label class="form-check-label" for="id-mode-configuration">{{#str}} evaluationmodeconfiguration, tool_analytics {{/str}}</label>
</div>

View File

@ -0,0 +1,83 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_analytics/evaluation_options
Evaluation selector.
The purpose of this template is to render the evaluation mode options.
Classes required for JS:
* none
Data attributes required for JS:
* none
Example context (json):
{
"trainedexternally": "1",
"timesplittingmethods": [
{
"id": "ou",
"name": "Quarters"
}, {
"id": "yeah",
"name": "Tenths"
}
]
}
}}
{{#trainedexternally}}
<div class="box mb-4">{{#str}} evaluationmodeinfo, tool_analytics {{/str}}</div>
<div class="custom-control custom-radio">
<input class="custom-control-input" type="radio" name="evaluationmode" id="id-mode-trainedmodel" value="trainedmodel" checked>
<label class="custom-control-label" for="id-mode-trainedmodel">{{#str}} evaluationmodetrainedmodel, tool_analytics {{/str}}</label>
</div>
<div class="custom-control custom-radio">
<input class="custom-control-input" type="radio" name="evaluationmode" id="id-mode-configuration" value="configuration">
<label class="custom-control-label" for="id-mode-configuration">{{#str}} evaluationmodeconfiguration, tool_analytics {{/str}}</label>
</div>
{{/trainedexternally}}
{{! Hidden by default if #trainedexternally as the default option is trainedmodel in this case.}}
<div id="id-evaluation-timesplitting-container" class="m-t-1 {{#trainedexternally}}hidden{{/trainedexternally}}">
{{#str}} selecttimesplittingforevaluation, tool_analytics {{/str}}
<div>
<select id="id-evaluation-timesplitting" name="timesplitting" class="custom-select m-t-1">
{{#timesplittingmethods}}
<option value="{{id}}">{{text}}</option>
{{/timesplittingmethods}}
</select>
</div>
</div>
{{#js}}
require(['jquery'], function($) {
$("input[name='evaluationmode']:radio").change(function() {
console.log('asd' + $(this).val());
if ($(this).val() == 'configuration') {
$('#id-evaluation-timesplitting-container').show();
} else {
$('#id-evaluation-timesplitting-container').hide();
}
});
});
{{/js}}