Completed
Push — master ( f46779...ac850c )
by Corey
06:30 queued 01:05
created

common/models/UserBehavior.php (2 issues)

1
<?php
2
3
namespace common\models;
4
5
use Yii;
6
use \common\interfaces\TimeInterface;
7
use \common\interfaces\BehaviorInterface;
8
use \common\interfaces\UserBehaviorInterface;
9
use \common\components\ActiveRecord;
10
use yii\db\Query;
11
use yii\helpers\ArrayHelper as AH;
12
use \DateTime;
13
use \DateTimeZone;
14
use yii\db\Expression;
15
16
/**
17
 * This is the model class for table "user_behavior_link".
18
 *
19
 * @property integer $id
20
 * @property integer $user_id
21
 * @property integer $behavior_id
22
 * @property string $date
23
 *
24
 * @property Behavior $user
25
 */
26
class UserBehavior extends ActiveRecord implements UserBehaviorInterface
27
{
28
  private $time;
29
  private $behavior;
30
31
  public function __construct(BehaviorInterface $behavior, TimeInterface $time, $config = []) {
32
    $this->behavior = $behavior;
33
    $this->time = $time;
34
    parent::__construct($config);
35
  }
36
37
  /**
38
   * @inheritdoc
39
   */
40
  public static function tableName()
41
  {
42
    return 'user_behavior_link';
43
  }
44
45
  /**
46
   * @inheritdoc
47
   */
48
  public function rules()
49
  {
50
    return [
51
      [['user_id', 'behaviorr_id', 'date'], 'required'],
52
      [['user_id', 'behaviorr_id'], 'integer'],
53
      //[['date'], 'string']
54
    ];
55
  }
56
57
  /**
58
   * @inheritdoc
59
   */
60
  public function attributeLabels()
61
  {
62
    return [
63
      'id'        => 'ID',
64
      'date'      => 'Date',
65
      'user_id'   => 'User ID',
66
      'behavior_id' => 'Behavior ID',
67
    ];
68
  }
69
70
  /**
71
   * @return \yii\db\ActiveQuery
72
   */
73
  public function getUser()
74
  {
75
    return $this->hasOne(\common\models\User::className(), ['id' => 'user_id']);
76
  }
77
78
  public function getPastCheckinDates()
79
  {
80
    $past_checkin_dates = [];
81
    $query = new Query;
82
    $query->params = [":user_id" => Yii::$app->user->id];
83
    $query->select("date")
84
      ->from('user_behavior_link l')
85
      ->groupBy('date, user_id')
86
      ->having('user_id = :user_id');
87
    $temp_dates = $query->all();
88
    foreach($temp_dates as $temp_date) {
89
      $past_checkin_dates[] = $this->time->convertUTCToLocal($temp_date['date']);
90
    }
91
92
    return $past_checkin_dates;
93
  }
94
95
  public function getUserBehaviorsWithCategory($checkin_date) {
96
    list($start, $end) = $this->time->getUTCBookends($checkin_date);
97
98
    $query = new Query;
99
    $query->select('*')
100
      ->from('user_behavior_link')
101
      ->orderBy('behavior_id')
102
      ->where("user_id=:user_id
103
          AND date > :start_date
104
          AND date < :end_date",
105
      [
106
        ":user_id" => Yii::$app->user->id, 
107
        ":start_date" => $start, 
108
        ":end_date" => $end
109
      ]);
110
111
    $user_behaviors = self::decorateWithCategory($query->all());
112
    return AH::map($user_behaviors, 'id', function($a) { return $a['behavior']['name']; }, function($b) {return $b['behavior']['category_id']; });
113
  }
114
115
  public function getDailyScore($date = null) {
116
    // default to today's score
117
    if(is_null($date)) $date = $this->time->getLocalDate();
118
119
    list($start, $end) = $this->time->getUTCBookends($date);
120
    $score = $this->calculateScoreByUTCRange($start, $end);
121
    return reset($score) ?: 0; // get first array item
0 ignored issues
show
$score of type integer is incompatible with the type array expected by parameter $array of reset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

121
    return reset(/** @scrutinizer ignore-type */ $score) ?: 0; // get first array item
Loading history...
122
  }
123
124
  public function getBehaviorsByDate($start, $end) {
125
      $uo = $this->find()
126
        ->select(['id', 'user_id', 'behavior_id', 'date'])
127
        ->where(
128
          "user_id=:user_id AND date > :start_date AND date <= :end_date",
129
          ["user_id" => Yii::$app->user->id, ':start_date' => $start, ":end_date" => $end]
130
        )
131
        ->orderBy('date')
132
        ->asArray()
133
        ->all();
134
135
      return self::decorate($uo, false);
136
  }
137
138
  /**
139
   * @date date string in yyyy-mm-dd format
140
   * @return int score
141
   */
142
  public function calculateScoreByUTCRange($start, $end) {
143
    $behaviors = $this->getBehaviorsByDate($start, $end);
144
    $scores = $this->calculateScore($behaviors);
145
146
    return $scores;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $scores returns the type array which is incompatible with the documented return type integer.
Loading history...
147
  }
148
149
  public function calculateScoresOfLastMonth() {
150
    $key = "scores_of_last_month_".Yii::$app->user->id."_".$this->time->getLocalDate();
151
    $scores = Yii::$app->cache->get($key);
152
    if($scores === false) {
153
      $scores = [];
154
155
      $dt = new DateTime("now", new DateTimeZone("UTC"));
156
      $end = $dt->format("Y-m-d H:i:s");
157
      $start = $dt->modify("-1 month")->format("Y-m-d H:i:s");
158
159
      $behaviors = $this->getBehaviorsByDate($start, $end);
160
      $scores = $this->calculateScore($behaviors);
161
162
      Yii::$app->cache->set($key, $scores, 60*60*24);
163
    }
164
165
    return $scores;
166
  }
167
168
  public function calculateScore($usr_bhvrs, $all_cats = null) {
169
    if(!!!$usr_bhvrs) return [];
170
    if(!!!$all_cats)  $all_cats = $this->behavior->getCategories();
171
172
    $usr_bhvrs = array_reduce($usr_bhvrs, function($carry, $bhvr) {
173
      $date = $this->time->convertUTCToLocal($bhvr['date']);
174
      $carry[$date][] = $bhvr['behavior'];
175
      return $carry;
176
    }, []);
177
178
    $scores = [];
179
    foreach($usr_bhvrs as $date => $bhvrs) {
180
      $picked = array_reduce($bhvrs, function($carry, $bhvr) {
181
        $carry[$bhvr['category_id']][] = $bhvr['id'];
182
        return $carry;
183
      }, []);
184
185
      $grades_sum = array_sum($this->_getCatGrades($picked, $all_cats));
186
      $scores[$date] = intval(floor($grades_sum));
187
    }
188
189
    return $scores;
190
  }
191
192
  private function _getCatGrades($picked, $all_cats = null) {
193
    if(!!!$all_cats) $all_cats = $this->behavior->getCategories();
194
195
    return array_reduce($all_cats, function($carry, $cat) use($picked) {
196
      if(array_key_exists($cat['category_id'], $picked)) {
197
        $count = count($picked[$cat['category_id']]);
198
        $prcnt_2x = ($count / $cat['behavior_count']) * 2;
199
        // because we're doubling the % we want to ensure we don't take more than 100%
200
        $carry[$cat['category_id']] = $cat['weight'] * min($prcnt_2x, 1);
201
      } else {
202
        $carry[$cat['category_id']] = 0;
203
      }
204
      return $carry;
205
    }, []);
206
  }
207
208
  /**
209
   * Returns a list of the most-selected behaviors
210
   * @param integer $limit the desired number of behaviors
211
   */
212
  public function getTopBehaviors($limit = 5) {
213
    return self::decorateWithCategory($this->getBehaviorsWithCounts($limit));
214
  }
215
216
  /**
217
   * Returns a list of categories and the number of selected behaviors in each category
218
   */
219
  public function getBehaviorsByCategory() {
220
    return array_values(array_reduce(self::decorateWithCategory($this->getBehaviorsWithCounts()), function($acc, $row) {
221
      $cat_id = $row['behavior']['category']['id'];
222
      if(array_key_exists($cat_id, $acc)) {
223
        $acc[$cat_id]['count'] += $row['count'];
224
      } else {
225
        $acc[$cat_id] = [
226
          'name' => $row['behavior']['category']['name'],
227
          'count' => $row['count'],
228
        ];
229
      }
230
      return $acc;
231
    }, []));
232
  }
233
234
  public static function decorate(Array $uo, $with_category = false) {
235
    foreach($uo as &$o) {
236
      if($behavior = \common\models\Behavior::getBehavior('id', $o['behavior_id'])) {
237
        $o['behavior'] = $behavior;
238
        if($with_category) {
239
          $o['behavior']['category'] = \common\models\Category::getCategory('id', $o['behavior']['category_id']);
240
        }
241
      }
242
    }
243
    return $uo;
244
  }
245
246
  public static function decorateWithCategory(Array $uo) {
247
    return self::decorate($uo, true);
248
  }
249
250
  public function getBehaviorsWithCounts($limit = null) {
251
    $query = new Query;
252
    $query->params = [":user_id" => Yii::$app->user->id];
253
    $query->select("user_id, behavior_id, COUNT(id) as count")
254
      ->from('user_behavior_link')
255
      ->groupBy('behavior_id, user_id')
256
      ->having('user_id = :user_id')
257
      ->orderBy('count DESC');
258
    if($limit) {
259
      $query->limit($limit);
260
    }
261
262
    return $query->all();
263
  }
264
}
265