7c6aa3adcc1fe66820d1c5e70a5addb8b1d212a3
[moodle.git] / analytics / classes / local / indicator / base.php
1 <?php
2 // This file is part of Moodle - http://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/>.
17 /**
18  * Abstract base indicator.
19  *
20  * @package   core_analytics
21  * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core_analytics\local\indicator;
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * Abstract base indicator.
31  *
32  * @package   core_analytics
33  * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
34  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 abstract class base extends \core_analytics\calculable {
38     /**
39      * Min value an indicator can return.
40      */
41     const MIN_VALUE = -1;
43     /**
44      * Max value an indicator can return.
45      */
46     const MAX_VALUE = 1;
48     /**
49      * Converts the calculated indicators to dataset feature/s.
50      *
51      * @param float|int[] $calculatedvalues
52      * @return array
53      */
54     abstract protected function to_features($calculatedvalues);
56     /**
57      * Calculates the sample.
58      *
59      * Return a value from self::MIN_VALUE to self::MAX_VALUE or null if the indicator can not be calculated for this sample.
60      *
61      * @param int $sampleid
62      * @param string $sampleorigin
63      * @param integer $starttime Limit the calculation to this timestart
64      * @param integer $endtime Limit the calculation to this timeend
65      * @return float|null
66      */
67     abstract protected function calculate_sample($sampleid, $sampleorigin, $starttime, $endtime);
69     /**
70      * Should this value be displayed?
71      *
72      * Indicators providing multiple features can be used this method to discard some of them.
73      *
74      * @param float $value
75      * @param string $subtype
76      * @return bool
77      */
78     public function should_be_displayed($value, $subtype) {
79         // We should everything by default.
80         return true;
81     }
83     /**
84      * Allows indicators to specify data they need.
85      *
86      * e.g. A model using courses as samples will not provide users data, but an indicator like
87      * "user is hungry" needs user data.
88      *
89      * @return null|string[] Name of the required elements (use the database tablename)
90      */
91     public static function required_sample_data() {
92         return null;
93     }
95     /**
96      * Returns an instance of the indicator.
97      *
98      * Useful to reset cached data.
99      *
100      * @return \core_analytics\local\indicator\base
101      */
102     public static function instance() {
103         return new static();
104     }
106     /**
107      * Returns the maximum value an indicator calculation can return.
108      *
109      * @return float
110      */
111     public static function get_max_value() {
112         return self::MAX_VALUE;
113     }
115     /**
116      * Returns the minimum value an indicator calculation can return.
117      *
118      * @return float
119      */
120     public static function get_min_value() {
121         return self::MIN_VALUE;
122     }
124     /**
125      * Hook to allow indicators to pre-fill data that is shared accross time range calculations.
126      *
127      * Useful to fill analysable-dependant data that does not depend on the time ranges. Use
128      * instance vars to cache data that can be re-used across samples calculations but changes
129      * between time ranges (indicator instances are reset between time ranges to avoid unexpected
130      * problems).
131      *
132      * You are also responsible of emptying previous analysable caches.
133      *
134      * @param \core_analytics\analysable $analysable
135      * @return void
136      */
137     public function fill_per_analysable_caches(\core_analytics\analysable $analysable) {
138     }
140     /**
141      * Calculates the indicator.
142      *
143      * Returns an array of values which size matches $sampleids size.
144      *
145      * @param int[] $sampleids
146      * @param string $samplesorigin
147      * @param integer $starttime Limit the calculation to this timestart
148      * @param integer $endtime Limit the calculation to this timeend
149      * @param array $existingcalculations Existing calculations of this indicator, indexed by sampleid.
150      * @return array array[0] with format [sampleid] = int[]|float[], array[1] with format [sampleid] = int|float
151      */
152     public function calculate($sampleids, $samplesorigin, $starttime = false, $endtime = false, $existingcalculations = array()) {
154         if (!PHPUNIT_TEST && CLI_SCRIPT) {
155             echo '.';
156         }
158         $calculations = array();
159         $newcalculations = array();
160         foreach ($sampleids as $sampleid => $unusedsampleid) {
162             if (isset($existingcalculations[$sampleid])) {
163                 $calculatedvalue = $existingcalculations[$sampleid];
164             } else {
165                 $calculatedvalue = $this->calculate_sample($sampleid, $samplesorigin, $starttime, $endtime);
166                 $newcalculations[$sampleid] = $calculatedvalue;
167             }
169             if (!is_null($calculatedvalue) && ($calculatedvalue > self::MAX_VALUE || $calculatedvalue < self::MIN_VALUE)) {
170                 throw new \coding_exception('Calculated values should be higher than ' . self::MIN_VALUE .
171                     ' and lower than ' . self::MAX_VALUE . ' ' . $calculatedvalue . ' received');
172             }
174             $calculations[$sampleid] = $calculatedvalue;
175         }
177         $features = $this->to_features($calculations);
179         return array($features, $newcalculations);
180     }