MDL-64320 analytics: Validate discrete indicators separately
[moodle.git] / analytics / classes / local / indicator / base.php
CommitLineData
369389c9
DM
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/>.
16
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 */
24
25namespace core_analytics\local\indicator;
26
27defined('MOODLE_INTERNAL') || die();
28
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 */
36abstract class base extends \core_analytics\calculable {
37
413f19bc
DM
38 /**
39 * Min value an indicator can return.
40 */
369389c9
DM
41 const MIN_VALUE = -1;
42
413f19bc
DM
43 /**
44 * Max value an indicator can return.
45 */
369389c9
DM
46 const MAX_VALUE = 1;
47
369389c9 48 /**
413f19bc 49 * Converts the calculated indicators to dataset feature/s.
369389c9
DM
50 *
51 * @param float|int[] $calculatedvalues
52 * @return array
53 */
54 abstract protected function to_features($calculatedvalues);
55
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);
68
413f19bc
DM
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 */
369389c9
DM
78 public function should_be_displayed($value, $subtype) {
79 // We should everything by default.
80 return true;
81 }
82
83 /**
413f19bc
DM
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)
369389c9
DM
90 */
91 public static function required_sample_data() {
92 return null;
93 }
94
413f19bc
DM
95 /**
96 * Returns an instance of the indicator.
97 *
98 * Useful to reset cached data.
99 *
100 * @return \core_analytics\local\indicator\base
101 */
369389c9
DM
102 public static function instance() {
103 return new static();
104 }
105
413f19bc
DM
106 /**
107 * Returns the maximum value an indicator calculation can return.
108 *
109 * @return float
110 */
369389c9
DM
111 public static function get_max_value() {
112 return self::MAX_VALUE;
113 }
114
413f19bc
DM
115 /**
116 * Returns the minimum value an indicator calculation can return.
117 *
118 * @return float
119 */
369389c9
DM
120 public static function get_min_value() {
121 return self::MIN_VALUE;
122 }
123
fbc889a2
DM
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 }
139
369389c9
DM
140 /**
141 * Calculates the indicator.
142 *
143 * Returns an array of values which size matches $sampleids size.
144 *
413f19bc 145 * @param int[] $sampleids
369389c9
DM
146 * @param string $samplesorigin
147 * @param integer $starttime Limit the calculation to this timestart
148 * @param integer $endtime Limit the calculation to this timeend
0690a271 149 * @param array $existingcalculations Existing calculations of this indicator, indexed by sampleid.
aa1b04dc 150 * @return array [0] = [$sampleid => int[]|float[]], [1] = [$sampleid => int|float], [2] = [$sampleid => $sampleid]
369389c9 151 */
0690a271 152 public function calculate($sampleids, $samplesorigin, $starttime = false, $endtime = false, $existingcalculations = array()) {
369389c9
DM
153
154 if (!PHPUNIT_TEST && CLI_SCRIPT) {
155 echo '.';
156 }
157
158 $calculations = array();
0690a271 159 $newcalculations = array();
aa1b04dc 160 $notnulls = array();
369389c9
DM
161 foreach ($sampleids as $sampleid => $unusedsampleid) {
162
0690a271
DM
163 if (isset($existingcalculations[$sampleid])) {
164 $calculatedvalue = $existingcalculations[$sampleid];
165 } else {
166 $calculatedvalue = $this->calculate_sample($sampleid, $samplesorigin, $starttime, $endtime);
167 $newcalculations[$sampleid] = $calculatedvalue;
168 }
369389c9 169
aa1b04dc
DM
170 if (!is_null($calculatedvalue)) {
171 $notnulls[$sampleid] = $sampleid;
e0c88997 172 $this->validate_calculated_value($calculatedvalue);
369389c9
DM
173 }
174
175 $calculations[$sampleid] = $calculatedvalue;
176 }
177
0690a271 178 $features = $this->to_features($calculations);
369389c9 179
aa1b04dc 180 return array($features, $newcalculations, $notnulls);
369389c9 181 }
e0c88997
DM
182
183 /**
184 * Validates the calculated value.
185 *
186 * @throws \coding_exception
187 * @param float $calculatedvalue
188 * @return true
189 */
190 protected function validate_calculated_value($calculatedvalue) {
191 if ($calculatedvalue > self::MAX_VALUE || $calculatedvalue < self::MIN_VALUE) {
192 throw new \coding_exception('Calculated values should be higher than ' . self::MIN_VALUE .
193 ' and lower than ' . self::MAX_VALUE . ' ' . $calculatedvalue . ' received');
194 }
195 return true;
196 }
369389c9 197}