MDL-65633 analytics: New interfaces for time-splitting methods
authorDavid Monllaó <davidm@moodle.com>
Fri, 24 May 2019 11:58:46 +0000 (13:58 +0200)
committerDavid Monllaó <davidm@moodle.com>
Thu, 18 Jul 2019 16:38:13 +0000 (18:38 +0200)
17 files changed:
admin/tool/analytics/classes/output/models_list.php
admin/tool/analytics/model.php
analytics/classes/local/target/base.php
analytics/classes/local/time_splitting/accumulative_parts.php
analytics/classes/local/time_splitting/after_now.php [new file with mode: 0644]
analytics/classes/local/time_splitting/before_now.php [new file with mode: 0644]
analytics/classes/local/time_splitting/equal_parts.php
analytics/classes/local/time_splitting/upcoming_periodic.php
analytics/classes/model.php
analytics/tests/fixtures/test_target_shortname.php
analytics/tests/model_test.php
analytics/upgrade.txt
course/classes/analytics/target/course_enrolments.php
course/classes/analytics/target/no_teaching.php
lang/en/analytics.php
lib/classes/analytics/time_splitting/single_range.php
user/classes/analytics/target/upcoming_activities_due.php

index 5002af0..b459933 100644 (file)
@@ -96,17 +96,6 @@ class models_list implements \renderable, \templatable {
             $onlycli = 1;
         }
 
-        // Evaluation options.
-        $timesplittingmethods = [
-            ['id' => 'all', 'text' => get_string('alltimesplittingmethods', 'tool_analytics')],
-        ];
-        foreach (\core_analytics\manager::get_time_splitting_methods_for_evaluation(true) 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($output);
@@ -216,7 +205,22 @@ class models_list implements \renderable, \templatable {
 
                 $actionid = 'evaluate-' . $model->get_id();
 
-                $modeltimesplittingmethods = $timesplittingmethods;
+                // Evaluation options.
+                $modeltimesplittingmethods = [
+                    ['id' => 'all', 'text' => get_string('alltimesplittingmethods', 'tool_analytics')],
+                ];
+                $potentialtimesplittingmethods = $model->get_potential_timesplittings();
+                foreach (\core_analytics\manager::get_time_splitting_methods_for_evaluation(true) as $timesplitting) {
+                    if (empty($potentialtimesplittingmethods[$timesplitting->get_id()])) {
+                        // This time-splitting method can not be used for this model.
+                        continue;
+                    }
+                    $modeltimesplittingmethods[] = [
+                        'id' => \tool_analytics\output\helper::class_to_option($timesplitting->get_id()),
+                        'text' => $timesplitting->get_name()->out(),
+                    ];
+                }
+
                 // Include the current time-splitting method as the default selection method the model already have one.
                 if ($model->get_model_obj()->timesplitting) {
                     $currenttimesplitting = ['id' => 'current', 'text' => get_string('currenttimesplitting', 'tool_analytics')];
index 4174ce1..ba49d7e 100644 (file)
@@ -118,7 +118,7 @@ switch ($action) {
             'trainedmodel' => $model->is_trained(),
             'staticmodel' => $model->is_static(),
             'indicators' => $model->get_potential_indicators(),
-            'timesplittings' => \core_analytics\manager::get_all_time_splittings(),
+            'timesplittings' => $model->get_potential_timesplittings(),
             'predictionprocessors' => \core_analytics\manager::get_all_prediction_processors()
         );
         $mform = new \tool_analytics\output\form\edit_model(null, $customdata);
index e7438a6..ad17226 100644 (file)
@@ -104,6 +104,18 @@ abstract class base extends \core_analytics\calculable {
         return false;
     }
 
+    /**
+     * Can the provided time-splitting method be used on this target?.
+     *
+     * Time-splitting methods not matching the target requirements will not be selectable by models based on this target.
+     *
+     * @param  \core_analytics\local\time_splitting\base $timesplitting
+     * @return bool
+     */
+    public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
+        return true;
+    }
+
     /**
      * Update the last analysis time on analysable processed or always.
      *
index 45fd2a8..f5d84bb 100644 (file)
@@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class accumulative_parts extends base {
+abstract class accumulative_parts extends base implements before_now {
 
     /**
      * The number of parts to split the analysable duration in.
diff --git a/analytics/classes/local/time_splitting/after_now.php b/analytics/classes/local/time_splitting/after_now.php
new file mode 100644 (file)
index 0000000..2cda6c0
--- /dev/null
@@ -0,0 +1,37 @@
+<?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/>.
+
+/**
+ * Interface for time-splitting methods whose ranges' times are after time().
+ *
+ * @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\local\time_splitting;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Interface for time-splitting methods whose ranges' times are after time().
+ *
+ * @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 after_now {
+}
diff --git a/analytics/classes/local/time_splitting/before_now.php b/analytics/classes/local/time_splitting/before_now.php
new file mode 100644 (file)
index 0000000..8379cf7
--- /dev/null
@@ -0,0 +1,37 @@
+<?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/>.
+
+/**
+ * Interface for time-splitting methods whose ranges' times are before time().
+ *
+ * @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\local\time_splitting;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Interface for time-splitting methods whose ranges' times are before time().
+ *
+ * @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 before_now {
+}
index 7d48858..9683492 100644 (file)
@@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class equal_parts extends base {
+abstract class equal_parts extends base implements before_now {
 
     /**
      * Returns the number of parts the analyser duration should be split in.
index 4960c73..7b3c9c6 100644 (file)
@@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class upcoming_periodic extends periodic {
+abstract class upcoming_periodic extends periodic implements after_now {
 
     /**
      * The next range indicator calculations should be based on upcoming dates.
index 41345ae..93d653e 100644 (file)
@@ -238,7 +238,7 @@ class model {
         $indicators = \core_analytics\manager::get_all_indicators();
 
         if (empty($this->analyser)) {
-            $this->init_analyser(array('evaluation' => true));
+            $this->init_analyser(array('notimesplitting' => true));
         }
 
         foreach ($indicators as $classname => $indicator) {
@@ -281,15 +281,24 @@ class model {
             throw new \moodle_exception('errornotarget', 'analytics');
         }
 
+        $potentialtimesplittings = $this->get_potential_timesplittings();
+
         $timesplittings = array();
         if (empty($options['notimesplitting'])) {
             if (!empty($options['evaluation'])) {
                 // The evaluation process will run using all available time splitting methods unless one is specified.
                 if (!empty($options['timesplitting'])) {
                     $timesplitting = \core_analytics\manager::get_time_splitting($options['timesplitting']);
+
+                    if (empty($potentialtimesplittings[$timesplitting->get_id()])) {
+                        throw new \moodle_exception('errorcannotusetimesplitting', 'analytics');
+                    }
                     $timesplittings = array($timesplitting->get_id() => $timesplitting);
                 } else {
-                    $timesplittings = \core_analytics\manager::get_time_splitting_methods_for_evaluation();
+                    $timesplittingsforevaluation = \core_analytics\manager::get_time_splitting_methods_for_evaluation();
+
+                    // They both have the same objects, using $potentialtimesplittings as its items are sorted.
+                    $timesplittings = array_intersect_key($potentialtimesplittings, $timesplittingsforevaluation);
                 }
             } else {
 
@@ -327,6 +336,27 @@ class model {
         return \core_analytics\manager::get_time_splitting($this->model->timesplitting);
     }
 
+    /**
+     * Returns the time-splitting methods that can be used by this model.
+     *
+     * @return \core_analytics\local\time_splitting\base[]
+     */
+    public function get_potential_timesplittings() {
+
+        $timesplittings = \core_analytics\manager::get_all_time_splittings();
+        uasort($timesplittings, function($a, $b) {
+            return strcasecmp($a->get_name(), $b->get_name());
+        });
+
+        foreach ($timesplittings as $key => $timesplitting) {
+            if (!$this->get_target()->can_use_timesplitting($timesplitting)) {
+                unset($timesplittings[$key]);
+                continue;
+            }
+        }
+        return $timesplittings;
+    }
+
     /**
      * Creates a new model. Enables it if $timesplittingid is specified.
      *
index 6e9a715..5d94152 100644 (file)
@@ -91,6 +91,16 @@ class test_target_shortname extends \core_analytics\local\target\binary {
         return array();
     }
 
+    /**
+     * Only past stuff.
+     *
+     * @param  \core_analytics\local\time_splitting\base $timesplitting
+     * @return bool
+     */
+    public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
+        return ($timesplitting instanceof \core_analytics\local\time_splitting\before_now);
+    }
+
     /**
      * is_valid_analysable
      *
index 18a4fac..8e82a47 100644 (file)
@@ -94,7 +94,7 @@ class analytics_model_testcase extends advanced_testcase {
         $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
         $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
 
-        $this->model->enable('\core\analytics\time_splitting\no_splitting');
+        $this->model->enable('\core\analytics\time_splitting\single_range');
 
         $this->model->train();
         $this->model->predict();
@@ -139,7 +139,7 @@ class analytics_model_testcase extends advanced_testcase {
         $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
         $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
 
-        $this->model->enable('\core\analytics\time_splitting\no_splitting');
+        $this->model->enable('\core\analytics\time_splitting\single_range');
 
         $this->model->train();
         $this->model->predict();
@@ -494,6 +494,17 @@ class analytics_model_testcase extends advanced_testcase {
         $this->assertEquals($data->name['value'], '');
     }
 
+    /**
+     * Tests model::get_potential_timesplittings()
+     */
+    public function test_potential_timesplittings() {
+        $this->resetAfterTest();
+
+        $this->assertArrayNotHasKey('\core\analytics\time_splitting\no_splitting', $this->model->get_potential_timesplittings());
+        $this->assertArrayHasKey('\core\analytics\time_splitting\single_range', $this->model->get_potential_timesplittings());
+        $this->assertArrayHasKey('\core\analytics\time_splitting\quarters', $this->model->get_potential_timesplittings());
+    }
+
     /**
      * Generates a model log record.
      */
index 692c594..c8853c2 100644 (file)
@@ -6,6 +6,8 @@ information provided here is intended especially for developers.
 * "Time-splitting method" have been replaced by "Analysis interval" for the language strings that are
   displayed in the Moodle UI. The name of several time-splitting methods have been updated according
   to the new description of the field.
+* A new target::can_use_timesplitting method can be used to discard time-splitting methods that can not
+  be used on a target.
 
 === 3.7 ===
 
index 57b8ee6..4fbe33e 100644 (file)
@@ -50,6 +50,16 @@ abstract class course_enrolments extends \core_analytics\local\target\binary {
         return '\core\analytics\analyser\student_enrolments';
     }
 
+    /**
+     * Only past stuff.
+     *
+     * @param  \core_analytics\local\time_splitting\base $timesplitting
+     * @return bool
+     */
+    public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
+        return ($timesplitting instanceof \core_analytics\local\time_splitting\before_now);
+    }
+
     /**
      * Overwritten to show a simpler language string.
      *
index e1194ca..425e683 100644 (file)
@@ -44,6 +44,16 @@ class no_teaching extends \core_analytics\local\target\binary {
         return true;
     }
 
+    /**
+     * It requires a specific time-splitting method.
+     *
+     * @param  \core_analytics\local\time_splitting\base $timesplitting
+     * @return bool
+     */
+    public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
+        return (get_class($timesplitting) === \core\analytics\time_splitting\single_range::class);
+    }
+
     /**
      * Returns the name.
      *
index f768e8e..47a160b 100644 (file)
@@ -37,6 +37,7 @@ $string['defaultpredictoroption'] = 'Default processor ({$a})';
 $string['disabledmodel'] = 'Disabled model';
 $string['erroralreadypredict'] = 'File {$a} has already been used to generate predictions.';
 $string['errorcannotreaddataset'] = 'Dataset file {$a} cannot be read.';
+$string['errorcannotusetimesplitting'] = 'The provided analysis interval can not be used on this model.';
 $string['errorcannotwritedataset'] = 'Dataset file {$a} cannot be written.';
 $string['errorexportmodelresult'] = 'The machine learning model cannot be exported.';
 $string['errorimport'] = 'Error importing the provided JSON file.';
index 01d9d0d..55373b7 100644 (file)
@@ -33,7 +33,8 @@ 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 single_range extends \core_analytics\local\time_splitting\base {
+class single_range extends \core_analytics\local\time_splitting\base
+        implements \core_analytics\local\time_splitting\before_now {
 
     /**
      * Returns a lang_string object representing the name for the time spliting method.
index e58b5c8..b97ecbe 100644 (file)
@@ -54,6 +54,16 @@ class upcoming_activities_due extends \core_analytics\local\target\binary {
         return false;
     }
 
+    /**
+     * Only upcoming stuff.
+     *
+     * @param  \core_analytics\local\time_splitting\base $timesplitting
+     * @return bool
+     */
+    public function can_use_timesplitting(\core_analytics\local\time_splitting\base $timesplitting): bool {
+        return ($timesplitting instanceof \core_analytics\local\time_splitting\after_now);
+    }
+
     /**
      * Returns the name.
      *