jam-cloud/admin/app/models/cohort.rb

248 lines
8.6 KiB
Ruby

require 'date'
class Cohort < ActiveRecord::Base
KEY_SET_TOTAL = [:registered_users, :first_downloaded_client_at, :first_certified_gear_at, :music_sessions_user_history, 'play_jamtr', :jam_track_rights, :recorded_tracks, :friendships, :invited_users]
TOTAL_LABELS = {
registered_users: 'Registered Users',
first_downloaded_client_at: 'Downloaded app',
first_certified_gear_at: 'Certified Gear',
music_sessions_user_history: 'Played Online',
play_jamtr: 'Played JamTrack',
jam_track_rights: 'Purchased JamTrack',
recorded_tracks: 'Made Recording',
friendships: 'Connected w/Friend',
invited_users: 'Invite Others',
}
# KEY_SET_TOTAL = [:registered_users, :first_downloaded_client_at, :first_certified_gear_at, :music_session_histories, 'play_jamtr', :jam_track_rights, :recordings, :friendships, :invited_users]
KEY_SET_MONTH = [:registered_users, :first_downloaded_client_at, :first_certified_gear_at, :visit_1, :visit_2_5, :visit_6_, :play_online_1, :play_online_2_5, :play_online_6_, :play_jamtr_1, :play_jamtr_2_5, :play_jamtr_6_, :jam_track_rights_redeemed, :jam_track_rights, :recorded_tracks, :friendships, :invited_users]
MONTHLY_LABELS = {
registered_users: 'Registered Users',
first_downloaded_client_at: 'Downloaded app',
first_certified_gear_at: 'Certified Gear',
music_sessions_user_history_1: 'Played Online 1',
music_sessions_user_history_2_5: 'Played Online 2-5',
music_sessions_user_history_6_: 'Played Online 6+',
play_jamtr: 'Played JamTrack',
jam_track_rights: 'Purchased JamTrack',
recorded_tracks: 'Made Recording',
friendships: 'Connected w/Friend',
invited_users: 'Invite Others',
}
serialize :data_set, JSON
before_create do
self.data_set ||= {}
end
def self.date_tuples(from,to)
prec = from.size
start = Date.new(*from)
finish = Date.new(*to)
filter_on = [:day,:mon].first(3-prec)
filter = ->(d) { filter_on.all? {|attr| d.send(attr) == 1 } }
(start..finish)
.select(&filter)
.map { |d| [d.year,d.mon,d.day].first(prec) }
end
def self.cohort_group_ranges(starting=nil, ending=nil)
starting ||= User.where(admin: false).order(:created_at).first.created_at
ending ||= Time.now
dates = self.date_tuples([starting.year, starting.month], [ending.year, ending.month])
ranges = []
dates.each_with_index do |d1, idx|
d2 = dates[idx+1] || [Time.now.next_month.year,Time.now.next_month.month]
rr = Time.parse("#{d1[0]}-#{d1[1]}-1")..(Time.parse("#{d2[0]}-#{d2[1]}-1") - 1.second)
ranges << rr
end
ranges
end
def self.generate_monthly_cohorts(monthly_start, monthly_end)
Cohort.delete_all(['all_time = ?',false])
self.cohort_group_ranges.collect do |range|
next if range.first > monthly_start
cc = Cohort.new
cc.group_start = range.first
cc.group_end = range.last
cc.monthly_start = monthly_start
cc.monthly_end = monthly_end
cc.all_time = false
cc.save!
cc
end
end
def self.generate_all_time_cohorts
self.cohort_group_ranges.collect do |range|
unless cc = Cohort.where(group_start: range.first).where(all_time: true).limit(1).first
cc = Cohort.new
cc.group_start = range.first
cc.group_end = range.last
cc.all_time = true
cc.save!
end
cc
end
end
def _put_data_set(key, count, num_user)
self.data_set[key.to_s] = count
self.data_set["#{key}%"] = 100.0 * (count.to_f / num_user.to_f)
end
def self.cohort_users(cohort)
User.where(created_at: cohort.group_start..cohort.group_end)
end
def _played_online_subquery(constraint)
where = if constraint.is_a?(Range)
"played.cnt >= #{constraint.first} AND played.cnt <= #{constraint.last}"
else
"played.cnt #{constraint}"
end
start_date = all_time ? self.group_start : self.monthly_start
end_date = all_time ? self.group_end : self.monthly_end
sql =<<SQL
SELECT played.user_id FROM
(SELECT user_id, COUNT(*) cnt FROM music_sessions_user_history msuh1
WHERE
msuh1.created_at >= '#{start_date}' AND msuh1.created_at <= '#{end_date}' AND
EXTRACT(EPOCH FROM (msuh1.session_removed_at - msuh1.created_at)) >= 900 AND
(SELECT COUNT(*) FROM music_sessions_user_history msuh2
WHERE msuh1.music_session_id = msuh2.music_session_id
) > 1
GROUP BY user_id
) played
WHERE #{where}
SQL
end
def _subquery(assoc_key, num_user)
assoc = User.reflections[assoc_key]
start_date = all_time ? self.group_start : self.monthly_start
end_date = all_time ? self.group_end : self.monthly_end
sql =<<SQL
SELECT #{assoc.foreign_key} FROM #{assoc.class_name.constantize.table_name} tt
WHERE
tt.created_at >= '#{start_date}' AND
tt.created_at <= '#{end_date}'
SQL
yield(sql) if block_given?
self.class.cohort_users(self).where("users.id IN (#{sql})").count
end
def _monthly!
unless 0 < num_user = self.class.cohort_users(self).count
self.update_attribute(:data_set, {})
return
end
self.data_set['registered_users'] = num_user
num_user = num_user.to_f
qq = self.class.cohort_users(self)
.where(first_downloaded_client_at: self.monthly_start..self.monthly_end)
_put_data_set(:first_downloaded_client_at, qq.count, num_user)
qq = self.class.cohort_users(self)
.where(first_certified_gear_at: self.monthly_start..self.monthly_end)
_put_data_set(:first_certified_gear_at, qq.count, num_user)
count = _subquery(assoc_key = :invited_users, num_user)
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :recorded_tracks, num_user)
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :jam_track_rights, num_user)
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :jam_track_rights, num_user) do |subsql|
subsql += " AND tt.redeemed = 't' "
end
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :friendships, num_user)
_put_data_set(assoc_key, count, num_user)
sql = _played_online_subquery(' = 1 ')
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
_put_data_set(:music_sessions_user_history_1, count, num_user)
sql = _played_online_subquery(2..5)
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
_put_data_set(:music_sessions_user_history_2_5, count, num_user)
sql = _played_online_subquery(' >= 6')
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
_put_data_set(:music_sessions_user_history_6_, count, num_user)
self.save!
end
def _join_user_all_time(assoc_ref)
assoc_ref.active_record
.joins("INNER JOIN users AS uu ON uu.id = #{assoc_ref.foreign_key}")
.where(created_at: self.group_start..self.group_end)
.where(['uu.created_at >= ? AND uu.created_at <= ?', self.group_start, self.group_end])
end
def _all_time!
unless 0 < num_user = self.class.cohort_users(self).count
self.update_attribute(:data_set, {})
return
end
self.data_set['registered_users'] = num_user
num_user = num_user.to_f
count = self.class.cohort_users(self)
.where(['first_downloaded_client_at IS NOT NULL'])
.count
_put_data_set('first_downloaded_client_at', count, num_user)
count = self.class.cohort_users(self)
.where(['first_certified_gear_at IS NOT NULL'])
.count
_put_data_set('first_certified_gear_at', count, num_user)
count = _subquery(assoc_key = :invited_users, num_user)
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :recorded_tracks, num_user)
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :friendships, num_user)
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :jam_track_rights, num_user)
_put_data_set(assoc_key, count, num_user)
sql = _played_online_subquery(' >= 1')
count = self.class.cohort_users(self).where("users.id IN (#{sql})").count
_put_data_set(:music_sessions_user_history, count, num_user)
self.save!
end
def populate!
self.all_time ? _all_time! : _monthly!
end
def self.monthly_cohorts(monthly_start, monthly_end)
self.generate_monthly_cohorts(monthly_start, monthly_end).compact.each do |cc|
cc._monthly!
end
end
end