| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 | // This file is part of Moodle - https://moodle.org/
 | 
        
           |  |  | 3 | //
 | 
        
           |  |  | 4 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 5 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 6 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 7 | // (at your option) any later version.
 | 
        
           |  |  | 8 | //
 | 
        
           |  |  | 9 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 12 | // GNU General Public License for more details.
 | 
        
           |  |  | 13 | //
 | 
        
           |  |  | 14 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 15 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 | namespace core_analytics;
 | 
        
           |  |  | 18 |   | 
        
           | 1441 | ariadna | 19 | use core_analytics\tests\mlbackend_helper_trait;
 | 
        
           |  |  | 20 |   | 
        
           | 1 | efrain | 21 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 22 |   | 
        
           |  |  | 23 | require_once(__DIR__ . '/fixtures/test_indicator_fullname.php');
 | 
        
           |  |  | 24 | require_once(__DIR__ . '/fixtures/test_target_shortname.php');
 | 
        
           |  |  | 25 |   | 
        
           |  |  | 26 | /**
 | 
        
           |  |  | 27 |  * Unit tests for the analytics stats.
 | 
        
           |  |  | 28 |  *
 | 
        
           |  |  | 29 |  * @package     core_analytics
 | 
        
           |  |  | 30 |  * @category    test
 | 
        
           |  |  | 31 |  * @copyright 2019 David Mudrák <david@moodle.com>
 | 
        
           |  |  | 32 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 33 |  */
 | 
        
           | 1441 | ariadna | 34 | final class stats_test extends \advanced_testcase {
 | 
        
           |  |  | 35 |     use mlbackend_helper_trait;
 | 
        
           | 1 | efrain | 36 |   | 
        
           |  |  | 37 |     /**
 | 
        
           |  |  | 38 |      * Set up the test environment.
 | 
        
           |  |  | 39 |      */
 | 
        
           |  |  | 40 |     public function setUp(): void {
 | 
        
           | 1441 | ariadna | 41 |         parent::setUp();
 | 
        
           | 1 | efrain | 42 |   | 
        
           |  |  | 43 |         $this->setAdminUser();
 | 
        
           |  |  | 44 |     }
 | 
        
           |  |  | 45 |   | 
        
           |  |  | 46 |     /**
 | 
        
           |  |  | 47 |      * Test the {@link \core_analytics\stats::enabled_models()} implementation.
 | 
        
           |  |  | 48 |      */
 | 
        
           | 11 | efrain | 49 |     public function test_enabled_models(): void {
 | 
        
           | 1 | efrain | 50 |   | 
        
           |  |  | 51 |         $this->resetAfterTest(true);
 | 
        
           |  |  | 52 |   | 
        
           |  |  | 53 |         // By default, sites have {@link \core_course\analytics\target\no_teaching} and
 | 
        
           |  |  | 54 |         // {@link \core_user\analytics\target\upcoming_activities_due} enabled.
 | 
        
           |  |  | 55 |         $this->assertEquals(4, \core_analytics\stats::enabled_models());
 | 
        
           |  |  | 56 |   | 
        
           |  |  | 57 |         $model = \core_analytics\model::create(
 | 
        
           |  |  | 58 |             \core_analytics\manager::get_target('\core_course\analytics\target\course_dropout'),
 | 
        
           |  |  | 59 |             [
 | 
        
           |  |  | 60 |                 \core_analytics\manager::get_indicator('\core\analytics\indicator\any_write_action'),
 | 
        
           |  |  | 61 |             ]
 | 
        
           |  |  | 62 |         );
 | 
        
           |  |  | 63 |   | 
        
           |  |  | 64 |         // Purely adding a new model does not make it included in the stats.
 | 
        
           |  |  | 65 |         $this->assertEquals(4, \core_analytics\stats::enabled_models());
 | 
        
           |  |  | 66 |   | 
        
           |  |  | 67 |         // New models must be enabled to have them counted.
 | 
        
           |  |  | 68 |         $model->enable('\core\analytics\time_splitting\quarters');
 | 
        
           |  |  | 69 |         $this->assertEquals(5, \core_analytics\stats::enabled_models());
 | 
        
           |  |  | 70 |     }
 | 
        
           |  |  | 71 |   | 
        
           |  |  | 72 |     /**
 | 
        
           |  |  | 73 |      * Test the {@link \core_analytics\stats::predictions()} implementation.
 | 
        
           |  |  | 74 |      */
 | 
        
           | 11 | efrain | 75 |     public function test_predictions(): void {
 | 
        
           | 1441 | ariadna | 76 |         if (!self::is_mlbackend_python_configured()) {
 | 
        
           |  |  | 77 |             $this->markTestSkipped('mlbackend_python is not configured.');
 | 
        
           |  |  | 78 |         }
 | 
        
           | 1 | efrain | 79 |   | 
        
           |  |  | 80 |         $this->resetAfterTest(true);
 | 
        
           |  |  | 81 |   | 
        
           |  |  | 82 |         $model = \core_analytics\model::create(
 | 
        
           |  |  | 83 |             \core_analytics\manager::get_target('test_target_shortname'),
 | 
        
           |  |  | 84 |             [
 | 
        
           |  |  | 85 |                 \core_analytics\manager::get_indicator('test_indicator_fullname'),
 | 
        
           |  |  | 86 |             ]
 | 
        
           |  |  | 87 |         );
 | 
        
           |  |  | 88 |   | 
        
           |  |  | 89 |         $model->enable('\core\analytics\time_splitting\no_splitting');
 | 
        
           |  |  | 90 |   | 
        
           |  |  | 91 |         // Train the model.
 | 
        
           |  |  | 92 |         $this->getDataGenerator()->create_course(['shortname' => 'a', 'fullname' => 'a', 'visible' => 1]);
 | 
        
           |  |  | 93 |         $this->getDataGenerator()->create_course(['shortname' => 'b', 'fullname' => 'b', 'visible' => 1]);
 | 
        
           |  |  | 94 |         $model->train();
 | 
        
           |  |  | 95 |   | 
        
           |  |  | 96 |         // No predictions yet.
 | 
        
           |  |  | 97 |         $this->assertEquals(0, \core_analytics\stats::predictions());
 | 
        
           |  |  | 98 |   | 
        
           |  |  | 99 |         // Get one new prediction.
 | 
        
           |  |  | 100 |         $this->getDataGenerator()->create_course(['shortname' => 'aa', 'fullname' => 'aa', 'visible' => 0]);
 | 
        
           |  |  | 101 |         $result = $model->predict();
 | 
        
           |  |  | 102 |   | 
        
           |  |  | 103 |         $this->assertEquals(1, count($result->predictions));
 | 
        
           |  |  | 104 |         $this->assertEquals(1, \core_analytics\stats::predictions());
 | 
        
           |  |  | 105 |   | 
        
           |  |  | 106 |         // Nothing changes if there is no new prediction.
 | 
        
           |  |  | 107 |         $result = $model->predict();
 | 
        
           |  |  | 108 |         $this->assertFalse(isset($result->predictions));
 | 
        
           |  |  | 109 |         $this->assertEquals(1, \core_analytics\stats::predictions());
 | 
        
           |  |  | 110 |   | 
        
           |  |  | 111 |         // Get two more predictions, we have three in total now.
 | 
        
           |  |  | 112 |         $this->getDataGenerator()->create_course(['shortname' => 'bb', 'fullname' => 'bb', 'visible' => 0]);
 | 
        
           |  |  | 113 |         $this->getDataGenerator()->create_course(['shortname' => 'cc', 'fullname' => 'cc', 'visible' => 0]);
 | 
        
           |  |  | 114 |   | 
        
           |  |  | 115 |         $result = $model->predict();
 | 
        
           |  |  | 116 |         $this->assertEquals(2, count($result->predictions));
 | 
        
           |  |  | 117 |         $this->assertEquals(3, \core_analytics\stats::predictions());
 | 
        
           |  |  | 118 |     }
 | 
        
           |  |  | 119 |   | 
        
           |  |  | 120 |     /**
 | 
        
           |  |  | 121 |      * Test the {@link \core_analytics\stats::actions()} and {@link \core_analytics\stats::actions_not_useful()} implementation.
 | 
        
           |  |  | 122 |      */
 | 
        
           | 11 | efrain | 123 |     public function test_actions(): void {
 | 
        
           | 1 | efrain | 124 |         global $DB;
 | 
        
           | 1441 | ariadna | 125 |   | 
        
           |  |  | 126 |         if (!self::is_mlbackend_python_configured()) {
 | 
        
           |  |  | 127 |             $this->markTestSkipped('mlbackend_python is not configured.');
 | 
        
           |  |  | 128 |         }
 | 
        
           |  |  | 129 |   | 
        
           | 1 | efrain | 130 |         $this->resetAfterTest(true);
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |         $model = \core_analytics\model::create(
 | 
        
           |  |  | 133 |             \core_analytics\manager::get_target('test_target_shortname'),
 | 
        
           |  |  | 134 |             [
 | 
        
           |  |  | 135 |                 \core_analytics\manager::get_indicator('test_indicator_fullname'),
 | 
        
           |  |  | 136 |             ]
 | 
        
           |  |  | 137 |         );
 | 
        
           |  |  | 138 |   | 
        
           |  |  | 139 |         $model->enable('\core\analytics\time_splitting\no_splitting');
 | 
        
           |  |  | 140 |   | 
        
           |  |  | 141 |         // Train the model.
 | 
        
           |  |  | 142 |         $this->getDataGenerator()->create_course(['shortname' => 'a', 'fullname' => 'a', 'visible' => 1]);
 | 
        
           |  |  | 143 |         $this->getDataGenerator()->create_course(['shortname' => 'b', 'fullname' => 'b', 'visible' => 1]);
 | 
        
           |  |  | 144 |         $model->train();
 | 
        
           |  |  | 145 |   | 
        
           |  |  | 146 |         // Generate two predictions.
 | 
        
           |  |  | 147 |         $this->getDataGenerator()->create_course(['shortname' => 'aa', 'fullname' => 'aa', 'visible' => 0]);
 | 
        
           |  |  | 148 |         $this->getDataGenerator()->create_course(['shortname' => 'bb', 'fullname' => 'bb', 'visible' => 0]);
 | 
        
           |  |  | 149 |         $model->predict();
 | 
        
           |  |  | 150 |   | 
        
           |  |  | 151 |         list($p1, $p2) = array_values($DB->get_records('analytics_predictions'));
 | 
        
           |  |  | 152 |   | 
        
           |  |  | 153 |         $p1 = new \core_analytics\prediction($p1, []);
 | 
        
           |  |  | 154 |         $p2 = new \core_analytics\prediction($p2, []);
 | 
        
           |  |  | 155 |   | 
        
           |  |  | 156 |         // No actions executed at the start.
 | 
        
           |  |  | 157 |         $this->assertEquals(0, \core_analytics\stats::actions());
 | 
        
           |  |  | 158 |         $this->assertEquals(0, \core_analytics\stats::actions_not_useful());
 | 
        
           |  |  | 159 |   | 
        
           |  |  | 160 |         // The user has acknowledged the first prediction.
 | 
        
           |  |  | 161 |         $p1->action_executed(\core_analytics\prediction::ACTION_FIXED, $model->get_target());
 | 
        
           |  |  | 162 |         $this->assertEquals(1, \core_analytics\stats::actions());
 | 
        
           |  |  | 163 |         $this->assertEquals(0, \core_analytics\stats::actions_not_useful());
 | 
        
           |  |  | 164 |   | 
        
           |  |  | 165 |         // The user has marked the other prediction as not useful.
 | 
        
           |  |  | 166 |         $p2->action_executed(\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED, $model->get_target());
 | 
        
           |  |  | 167 |         $this->assertEquals(2, \core_analytics\stats::actions());
 | 
        
           |  |  | 168 |         $this->assertEquals(1, \core_analytics\stats::actions_not_useful());
 | 
        
           |  |  | 169 |     }
 | 
        
           |  |  | 170 | }
 |