MDL-57791 insights: Clarify insights-prediction boundaries

This commit is contained in:
David Monllao 2017-06-09 17:05:29 +02:00
parent 1a8461390b
commit f9e7447f42
17 changed files with 177 additions and 69 deletions

View File

@ -57,6 +57,11 @@ class no_teaching extends \core_analytics\local\target\binary {
$sampledata = $prediction->get_sample_data();
$course = $sampledata['course'];
$url = new \moodle_url('/course/view.php', array('id' => $course->id));
$pix = new \pix_icon('i/course', get_string('enrolledusers', 'enrol'));
$actions['viewcourse'] = new \core_analytics\prediction_action('viewcourse', $prediction,
$url, $pix, get_string('view'));
if (has_capability('moodle/course:enrolreview', $sampledata['context'])) {
$url = new \moodle_url('/enrol/users.php', array('id' => $course->id));
$pix = new \pix_icon('i/enrolusers', get_string('enrolledusers', 'enrol'));

View File

@ -56,6 +56,7 @@ class models_list implements \renderable, \templatable {
$modeldata = $model->export();
// Model predictions list.
if ($model->uses_insights()) {
$predictioncontexts = $model->get_predictions_contexts();
if ($predictioncontexts) {
@ -67,17 +68,36 @@ class models_list implements \renderable, \templatable {
unset($predictioncontexts[$contextid]);
continue;
}
$predictioncontexts[$contextid] = shorten_text($context->get_context_name(true, true), 90);
// Special name for system level predictions as showing "System is not visually nice".
if ($contextid == SYSCONTEXTID) {
$contextname = get_string('allpredictions', 'tool_models');
} else {
$contextname = shorten_text($context->get_context_name(true, true), 90);
}
$predictioncontexts[$contextid] = $contextname;
}
\core_collator::asort($predictioncontexts);
if (!empty($predictioncontexts)) {
$url = new \moodle_url('/report/insights/insights.php', array('modelid' => $model->get_id()));
$singleselect = new \single_select($url, 'contextid', $predictioncontexts);
$modeldata->predictions = $singleselect->export_for_template($output);
$modeldata->insights = $singleselect->export_for_template($output);
}
}
if (empty($modeldata->insights)) {
if ($model->any_prediction_obtained()) {
$modeldata->noinsights = get_string('noinsights', 'analytics');
} else {
$modeldata->noinsights = get_string('nopredictionsyet', 'analytics');
}
}
} else {
$modeldata->noinsights = get_string('noinsightsmodel', 'analytics');
}
// Actions.
$actionsmenu = new \action_menu();
$actionsmenu->set_menu_trigger(get_string('actions'));

View File

@ -24,6 +24,7 @@
$string['accuracy'] = 'Accuracy';
$string['allindicators'] = 'All indicators';
$string['allpredictions'] = 'All predictions';
$string['analysingsitedata'] = 'Analysing the site';
$string['analyticmodels'] = 'Analytic models';
$string['bettercli'] = 'To evaluate models and to get predictions are heavy processes, it is better to run them through command line interface';
@ -53,6 +54,7 @@ $string['getpredictions'] = 'Get predictions';
$string['goodmodel'] = 'This is a good model and it can be used to predict, enable it to start getting predictions.';
$string['indicators'] = 'Indicators';
$string['info'] = 'Info';
$string['insights'] = 'Insights';
$string['labelstudentdropoutyes'] = 'Student at risk of dropping out';
$string['labelstudentdropoutno'] = 'Not at risk';
$string['labelteachingyes'] = 'Users with teaching capabilities have access to the course';
@ -88,6 +90,5 @@ $string['trainingprocessfinished'] = 'Training process finished';
$string['trainingresults'] = 'Training results';
$string['trainmodels'] = 'Train models';
$string['viewlog'] = 'Log';
$string['viewpredictions'] = 'View model predictions';
$string['weeksenddateautomaticallyset'] = 'End date automatically set based on start date and the number of sections';
$string['weeksenddatedefault'] = 'End date would be automatically calculated from the course start date';

View File

@ -45,7 +45,7 @@
<th scope="col">{{#str}}enabled, tool_models{{/str}}</th>
<th scope="col">{{#str}}indicators, tool_models{{/str}}</th>
<th scope="col">{{#str}}modeltimesplitting, tool_models{{/str}}</th>
<th scope="col">{{#str}}viewpredictions, tool_models{{/str}}</th>
<th scope="col">{{#str}}insights, tool_models{{/str}}</th>
<th scope="col">{{#str}}actions{{/str}}</th>
</tr>
</thead>
@ -72,12 +72,13 @@
{{#timesplitting}}{{timesplitting}}{{/timesplitting}}{{^timesplitting}}{{#str}}notdefined, tool_models{{/str}}{{/timesplitting}}
</td>
<td>
{{#predictions}}
{{! models_list renderer is responsible of sending one or the other}}
{{#insights}}
{{> core/single_select }}
{{/predictions}}
{{^predictions}}
{{#str}}nopredictionsyet, analytics{{/str}}
{{/predictions}}
{{/insights}}
{{#noinsights}}
{{.}}
{{/noinsights}}
</td>
<td>
{{#actions}}

View File

@ -36,8 +36,6 @@ require_once($CFG->dirroot . '/lib/gradelib.php');
*/
class course implements \core_analytics\analysable {
const MIN_STUDENT_LOGS_PERCENT = 90;
protected static $instances = array();
protected $studentroles = [];

View File

@ -83,6 +83,17 @@ abstract class base extends \core_analytics\calculable {
*/
abstract protected function calculate_sample($sampleid, \core_analytics\analysable $analysable, $starttime = false, $endtime = false);
/**
* Is this target generating insights?
*
* Defaults to true.
*
* @return bool
*/
public static function uses_insights() {
return true;
}
/**
* Based on facts (processed by machine learning backends) by default.
*

View File

@ -89,6 +89,9 @@ class model {
if (is_scalar($model)) {
$model = $DB->get_record('analytics_models', array('id' => $model));
if (!$model) {
throw new \moodle_exception('errorunexistingmodel', 'analytics', '', $model);
}
}
$this->model = $model;
}
@ -821,6 +824,31 @@ class model {
return $DB->get_records_sql($sql, array($this->model->id));
}
/**
* Has this model generated predictions?
*
* We don't check analytics_predictions table because targets have the ability to
* ignore some predicted values, if that is the case predictions are not even stored
* in db.
*
* @return bool
*/
public function any_prediction_obtained() {
global $DB;
return $DB->record_exists('analytics_predict_ranges',
array('modelid' => $this->model->id, 'timesplitting' => $this->model->timesplitting));
}
/**
* Whether this model generates insights or not (defined by the model's target).
*
* @return bool
*/
public function uses_insights() {
$target = $this->get_target();
return $target::uses_insights();
}
/**
* Whether predictions exist for this context.
*

View File

@ -42,6 +42,7 @@ $string['errorpredictwrongformat'] = 'The predictions processor return can not b
$string['errorprocessornotready'] = 'The selected predictions processor is not ready: {$a}';
$string['errorsamplenotavailable'] = 'The predicted sample is not available anymore';
$string['errorunexistingtimesplitting'] = 'The selected time splitting method is not available';
$string['errorunexistingmodel'] = 'Unexisting model {$a}';
$string['errorunknownaction'] = 'Unknown action';
$string['eventactionclicked'] = 'Prediction action clicked';
$string['indicator:accessesafterend'] = 'Accesses after the end date';
@ -61,6 +62,8 @@ $string['modeloutputdirinfo'] = 'Directory where prediction processors store all
$string['nocourses'] = 'No courses to analyse';
$string['nocoursestart'] = 'No course start';
$string['nodata'] = 'No data available';
$string['noinsightsmodel'] = 'This model does not generate insights';
$string['noinsights'] = 'No insights reported';
$string['nonewdata'] = 'No new data available';
$string['nonewtimeranges'] = 'No new time ranges, nothing to predict';
$string['nopredictionsyet'] = 'No predictions available yet';

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Prediction view page.
* Single insight view page.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
@ -27,13 +27,13 @@ namespace report_insights\output;
defined('MOODLE_INTERNAL') || die();
/**
* Prediction view page.
* Single insight view page.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class prediction implements \renderable, \templatable {
class insight implements \renderable, \templatable {
/**
* @var \core_analytics\model

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Predictions list page.
* Insights list page.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
@ -27,13 +27,13 @@ namespace report_insights\output;
defined('MOODLE_INTERNAL') || die();
/**
* Shows report_insights predictions list.
* Shows report_insights insights list.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class predictions_list implements \renderable, \templatable {
class insights_list implements \renderable, \templatable {
/**
* @var \core_analytics\model
@ -67,17 +67,29 @@ class predictions_list implements \renderable, \templatable {
$data = new \stdClass();
if ($this->model->uses_insights()) {
$predictions = $this->model->get_predictions($this->context);
$data->predictions = array();
$data->insights = array();
foreach ($predictions as $prediction) {
$predictionrenderable = new \report_insights\output\prediction($prediction, $this->model);
$data->predictions[] = $predictionrenderable->export_for_template($output);
$insightrenderable = new \report_insights\output\insight($prediction, $this->model);
$data->insights[] = $insightrenderable->export_for_template($output);
}
if (empty($data->predictions)) {
$notification = new \core\output\notification(get_string('nopredictionsyet', 'analytics'));
$data->nopredictions = $notification->export_for_template($output);
if (empty($data->insights)) {
if ($this->model->any_prediction_obtained()) {
$data->noinsights = get_string('noinsights', 'analytics');
} else {
$data->noinsights = get_string('nopredictionsyet', 'analytics');
}
}
} else {
$data->noinsights = get_string('noinsights', 'analytics');
}
if (!empty($data->noinsights)) {
$notification = new \core\output\notification($data->noinsights);
$data->noinsights = $notification->export_for_template($output);
}
if ($this->othermodels) {

View File

@ -40,25 +40,25 @@ use renderable;
class renderer extends plugin_renderer_base {
/**
* Renders the list of predictions
* Renders the list of insights
*
* @param renderable $renderable
* @return string HTML
*/
protected function render_predictions_list(renderable $renderable) {
protected function render_insights_list(renderable $renderable) {
$data = $renderable->export_for_template($this);
return parent::render_from_template('report_insights/predictions_list', $data);
return parent::render_from_template('report_insights/insights_list', $data);
}
/**
* Renders a prediction
* Renders an insight
*
* @param renderable $renderable
* @return string HTML
*/
protected function render_prediction(renderable $renderable) {
protected function render_insight(renderable $renderable) {
$data = $renderable->export_for_template($this);
return parent::render_from_template('report_insights/prediction_details', $data);
return parent::render_from_template('report_insights/insight_details', $data);
}
/**
@ -82,12 +82,12 @@ class renderer extends plugin_renderer_base {
}
/**
* Model without predictions info.
* Model without insights info.
*
* @param \context $context
* @return string HTML
*/
public function render_no_predictions(\context $context) {
public function render_no_insights(\context $context) {
global $OUTPUT, $PAGE;
// We don't want to disclose the name of the model if it has not been enabled.
@ -95,7 +95,27 @@ class renderer extends plugin_renderer_base {
$PAGE->set_heading($context->get_context_name());
$output = $OUTPUT->header();
$output .= $OUTPUT->notification(get_string('nopredictionsyet', 'analytics'), \core\output\notification::NOTIFY_INFO);
$output .= $OUTPUT->notification(get_string('noinsights', 'analytics'), \core\output\notification::NOTIFY_INFO);
$output .= $OUTPUT->footer();
return $output;
}
/**
* Model which target does not generate insights.
*
* @param \context $context
* @return string HTML
*/
public function render_no_insights_model(\context $context) {
global $OUTPUT, $PAGE;
// We don't want to disclose the name of the model if it has not been enabled.
$PAGE->set_title($context->get_context_name());
$PAGE->set_heading($context->get_context_name());
$output = $OUTPUT->header();
$output .= $OUTPUT->notification(get_string('noinsightsmodel', 'analytics'), \core\output\notification::NOTIFY_INFO);
$output .= $OUTPUT->footer();
return $output;

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* View model predictions.
* View model insights.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
@ -64,9 +64,9 @@ $PAGE->set_pagelayout('report');
$renderer = $PAGE->get_renderer('report_insights');
// No models with predictions available at this context level.
// No models with insights available at this context level.
if (!$modelid) {
echo $renderer->render_no_predictions($context);
echo $renderer->render_no_insights($context);
exit(0);
}
@ -77,18 +77,22 @@ $insightinfo->contextname = $context->get_context_name();
$insightinfo->insightname = $model->get_target()->get_name();
$title = get_string('insightinfo', 'analytics', $insightinfo);
if (!$model->is_enabled() && !has_capability('moodle/analytics:managemodels', $context)) {
echo $renderer->render_model_disabled($insightinfo);
exit(0);
}
if (!$model->uses_insights()) {
echo $renderer->render_no_insights_model($context);
exit(0);
}
$PAGE->set_title($title);
$PAGE->set_heading($title);
echo $OUTPUT->header();
$renderable = new \report_insights\output\predictions_list($model, $context, $othermodels);
$renderable = new \report_insights\output\insights_list($model, $context, $othermodels);
echo $renderer->render($renderable);
echo $OUTPUT->footer();

View File

@ -25,9 +25,9 @@
$string['disabledmodel'] = 'Sorry, this model has been disabled by the administrator';
$string['errorpredictionnotfound'] = 'Prediction not found';
$string['insight'] = 'Insight';
$string['insights'] = 'Insights';
$string['pluginname'] = 'Insights';
$string['prediction'] = 'Prediction';
$string['predictiondetails'] = 'Prediction details';
$string['predictions'] = 'Predictions';
$string['selectotherinsights'] = 'Select other insights...';

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* View a prediction.
* View an insight.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
@ -68,12 +68,17 @@ if (!$modelready && !has_capability('moodle/analytics:managemodels', $context))
exit(0);
}
if (!$model->uses_insights()) {
echo $renderer->render_no_insights_model($context);
exit(0);
}
$PAGE->set_title($title);
$PAGE->set_heading($title);
echo $OUTPUT->header();
$renderable = new \report_insights\output\prediction($prediction, $model);
$renderable = new \report_insights\output\insight($prediction, $model);
echo $renderer->render($renderable);
echo $OUTPUT->footer();

View File

@ -15,9 +15,9 @@
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template report_insights/prediction
@template report_insights/insight
Template for a prediction.
Template for a insight.
Classes required for JS:
* none

View File

@ -15,7 +15,7 @@
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template report_insights/prediction_details
@template report_insights/insight_details
Actions panel at the bottom of the assignment grading UI.
@ -30,7 +30,7 @@
}}
<h2>{{#str}}prediction, report_insights{{/str}}</h2>
{{> report_insights/prediction}}
{{> report_insights/insight}}
<h3>{{#str}} predictiondetails, report_insights {{/str}}</h3>
<div class="container prediction-calculations m-t-2">

View File

@ -15,9 +15,9 @@
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template report_insights/predictions_list
@template report_insights/insights_list
Template for the predictions list.
Template for the insights list.
Classes required for JS:
* none
@ -39,14 +39,14 @@
</div>
{{/modelselector}}
<h3>{{#str}} predictions, report_insights {{/str}}</h3>
<div class="predictions-list">
{{#predictions}}
{{> report_insights/prediction}}
{{/predictions}}
<h3>{{#str}} insights, report_insights {{/str}}</h3>
<div class="insights-list">
{{#insights}}
{{> report_insights/insight}}
{{/insights}}
</div>
{{#nopredictions}}
{{#noinsights}}
<div class="m-t-2">
{{> core/notification_info}}
</div>
{{/nopredictions}}
{{/noinsights}}