Updating internal analytics scripts
Result of some data sleuthing. They are only used for investigations, and not production aggregation. Hence, a bit ugly and unfinished.
This commit is contained in:
parent
b70f05fc9e
commit
c2abefb637
3 changed files with 488 additions and 141 deletions
scripts/analytics
184
scripts/analytics/mixpanelGetEvent.py
Normal file
184
scripts/analytics/mixpanelGetEvent.py
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
# Get mixpanel event data via export API
|
||||||
|
# Useful for debugging Mixpanel data weirdness
|
||||||
|
|
||||||
|
targetLevels = ['dungeons-of-kithgard', 'the-raised-sword', 'endangered-burl']
|
||||||
|
targetLevels = ['dungeons-of-kithgard']
|
||||||
|
eventFunnel = ['Started Level', 'Saw Victory']
|
||||||
|
# eventFunnel = ['Saw Victory']
|
||||||
|
# eventFunnel = ['Started Level']
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pprint import pprint
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from mixpanel import Mixpanel
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
# NOTE: mixpanel dates are by day and inclusive
|
||||||
|
# E.g. '2014-12-08' is any date that day, up to 2014-12-09 12am
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if not len(sys.argv) is 3:
|
||||||
|
print "Script format: <script> <api_key> <api_secret>"
|
||||||
|
else:
|
||||||
|
scriptStart = datetime.now()
|
||||||
|
|
||||||
|
api_key = sys.argv[1]
|
||||||
|
api_secret = sys.argv[2]
|
||||||
|
api = Mixpanel(
|
||||||
|
api_key = api_key,
|
||||||
|
api_secret = api_secret
|
||||||
|
)
|
||||||
|
|
||||||
|
startDate = '2015-01-01'
|
||||||
|
endDate = '2015-01-26'
|
||||||
|
|
||||||
|
startEvent = eventFunnel[0]
|
||||||
|
endEvent = eventFunnel[-1]
|
||||||
|
|
||||||
|
print("Requesting data for {0} to {1}".format(startDate, endDate))
|
||||||
|
data = api.request(['export'], {
|
||||||
|
# 'where': '"539c630f30a67c3b05d98d95" == properties["id"]',
|
||||||
|
# 'where': "('539c630f30a67c3b05d98d95' == properties['id'] or '539c630f30a67c3b05d98d95' == properties['distinct_id'])",
|
||||||
|
'event': eventFunnel,
|
||||||
|
'from_date': startDate,
|
||||||
|
'to_date': endDate
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
weirdUserIDs = []
|
||||||
|
eventUsers = {}
|
||||||
|
levelEventUserDayMap = {}
|
||||||
|
levelUserEventDayMap = {}
|
||||||
|
lines = data.split('\n')
|
||||||
|
print "Received %d entries" % len(lines)
|
||||||
|
for line in lines:
|
||||||
|
try:
|
||||||
|
if len(line) is 0: continue
|
||||||
|
eventData = json.loads(line)
|
||||||
|
# pprint(eventData)
|
||||||
|
# break
|
||||||
|
eventName = eventData['event']
|
||||||
|
if not eventName in eventFunnel:
|
||||||
|
print 'Unexpected event ' + eventName
|
||||||
|
break
|
||||||
|
if not 'properties' in eventData:
|
||||||
|
print('no properties, skpping')
|
||||||
|
continue
|
||||||
|
properties = eventData['properties']
|
||||||
|
if not 'distinct_id' in properties:
|
||||||
|
print('no distinct_id, skpping')
|
||||||
|
continue
|
||||||
|
user = properties['distinct_id']
|
||||||
|
if not 'time' in properties:
|
||||||
|
print('no time, skpping')
|
||||||
|
continue
|
||||||
|
time = properties['time']
|
||||||
|
pst = datetime.fromtimestamp(int(properties['time']))
|
||||||
|
utc = pst + timedelta(0, 8 * 60 * 60)
|
||||||
|
dateCreated = utc.isoformat()
|
||||||
|
day = dateCreated[0:10]
|
||||||
|
if day < startDate or day > endDate:
|
||||||
|
print "Skipping {0}".format(day)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'levelID' in properties:
|
||||||
|
level = properties['levelID']
|
||||||
|
elif 'level' in properties:
|
||||||
|
level = properties['level'].lower().replace(' ', '-')
|
||||||
|
else:
|
||||||
|
print("Unkonwn level for", eventName)
|
||||||
|
print(properties)
|
||||||
|
break
|
||||||
|
|
||||||
|
if not level in targetLevels: continue
|
||||||
|
|
||||||
|
# if user != "539c630f30a67c3b05d98d95": continue
|
||||||
|
pprint(eventData)
|
||||||
|
|
||||||
|
# if user == "54c1fc3a08652d5305442c6b":
|
||||||
|
# pprint(eventData)
|
||||||
|
# break
|
||||||
|
# if '-' in user:
|
||||||
|
# weirdUserIDs.append(user)
|
||||||
|
# # pprint(eventData)
|
||||||
|
# # break
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# print level
|
||||||
|
|
||||||
|
if not level in levelEventUserDayMap: levelEventUserDayMap[level] = {}
|
||||||
|
if not eventName in levelEventUserDayMap[level]: levelEventUserDayMap[level][eventName] = {}
|
||||||
|
if not user in levelEventUserDayMap[level][eventName] or levelEventUserDayMap[level][eventName][user] > day:
|
||||||
|
levelEventUserDayMap[level][eventName][user] = day
|
||||||
|
|
||||||
|
if not user in eventUsers: eventUsers[user] = True
|
||||||
|
|
||||||
|
if not level in levelUserEventDayMap: levelUserEventDayMap[level] = {}
|
||||||
|
if not user in levelUserEventDayMap[level]: levelUserEventDayMap[level][user] = {}
|
||||||
|
if not eventName in levelUserEventDayMap[level][user] or levelUserEventDayMap[level][user][eventName] > day:
|
||||||
|
levelUserEventDayMap[level][user][eventName] = day
|
||||||
|
|
||||||
|
except:
|
||||||
|
print "Unexpected error:", sys.exc_info()[0]
|
||||||
|
print line
|
||||||
|
break
|
||||||
|
|
||||||
|
# pprint(levelEventUserDayMap)
|
||||||
|
|
||||||
|
print("Weird user IDs: {0}".format(len(weirdUserIDs)))
|
||||||
|
|
||||||
|
for level in levelEventUserDayMap:
|
||||||
|
for event in levelEventUserDayMap[level]:
|
||||||
|
print("{0} {1} {2}".format(level, event, len(levelEventUserDayMap[level][event])))
|
||||||
|
print("Users: {0}".format(len(eventUsers)))
|
||||||
|
|
||||||
|
noStartDayUsers = []
|
||||||
|
levelFunnelData = {}
|
||||||
|
for level in levelUserEventDayMap:
|
||||||
|
for user in levelUserEventDayMap[level]:
|
||||||
|
# 6455
|
||||||
|
# for event in levelUserEventDayMap[level][user]:
|
||||||
|
# day = levelUserEventDayMap[level][user][event]
|
||||||
|
# if not level in levelFunnelData: levelFunnelData[level] = {}
|
||||||
|
# if not day in levelFunnelData[level]: levelFunnelData[level][day] = {}
|
||||||
|
# if not event in levelFunnelData[level][day]: levelFunnelData[level][day][event] = 0
|
||||||
|
# levelFunnelData[level][day][event] += 1
|
||||||
|
|
||||||
|
# 5382
|
||||||
|
funnelStartDay = None
|
||||||
|
for event in levelUserEventDayMap[level][user]:
|
||||||
|
day = levelUserEventDayMap[level][user][event]
|
||||||
|
if not level in levelFunnelData: levelFunnelData[level] = {}
|
||||||
|
if not day in levelFunnelData[level]: levelFunnelData[level][day] = {}
|
||||||
|
if not event in levelFunnelData[level][day]: levelFunnelData[level][day][event] = 0
|
||||||
|
if eventFunnel[0] == event:
|
||||||
|
levelFunnelData[level][day][event] += 1
|
||||||
|
funnelStartDay = day
|
||||||
|
break
|
||||||
|
|
||||||
|
if funnelStartDay:
|
||||||
|
for event in levelUserEventDayMap[level][user]:
|
||||||
|
if not event in levelFunnelData[level][funnelStartDay]:
|
||||||
|
levelFunnelData[level][funnelStartDay][event] = 0
|
||||||
|
if eventFunnel[0] != event:
|
||||||
|
levelFunnelData[level][funnelStartDay][event] += 1
|
||||||
|
for i in range(1, len(eventFunnel)):
|
||||||
|
event = eventFunnel[i]
|
||||||
|
if not event in levelFunnelData[level][funnelStartDay]:
|
||||||
|
levelFunnelData[level][funnelStartDay][event] = 0
|
||||||
|
else:
|
||||||
|
noStartDayUsers.append(user)
|
||||||
|
|
||||||
|
pprint(levelFunnelData)
|
||||||
|
print("No start day count: {0}".format(len(noStartDayUsers)))
|
||||||
|
noStartDayUsers.sort()
|
||||||
|
for i in range(len(noStartDayUsers)):
|
||||||
|
if i > 50: break
|
||||||
|
print(noStartDayUsers[i])
|
||||||
|
|
||||||
|
|
||||||
|
print("Script runtime: {0}".format(datetime.now() - scriptStart))
|
|
@ -1,10 +1,13 @@
|
||||||
# Calculate level completion rates via mixpanel export API
|
# Calculate level completion rates via mixpanel export API
|
||||||
|
|
||||||
# TODO: unique users
|
# TODO: why are our 'time' fields in PST time?
|
||||||
# TODO: align output
|
|
||||||
# TODO: order output
|
targetLevels = ['dungeons-of-kithgard', 'the-raised-sword', 'endangered-burl']
|
||||||
|
eventFunnel = ['Started Level', 'Saw Victory']
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from mixpanel import Mixpanel
|
from mixpanel import Mixpanel
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -19,23 +22,34 @@ if __name__ == '__main__':
|
||||||
if not len(sys.argv) is 3:
|
if not len(sys.argv) is 3:
|
||||||
print "Script format: <script> <api_key> <api_secret>"
|
print "Script format: <script> <api_key> <api_secret>"
|
||||||
else:
|
else:
|
||||||
|
scriptStart = datetime.now()
|
||||||
|
|
||||||
api_key = sys.argv[1]
|
api_key = sys.argv[1]
|
||||||
api_secret = sys.argv[2]
|
api_secret = sys.argv[2]
|
||||||
api = Mixpanel(
|
api = Mixpanel(
|
||||||
api_key = api_key,
|
api_key = api_key,
|
||||||
api_secret = api_secret
|
api_secret = api_secret
|
||||||
)
|
)
|
||||||
|
|
||||||
startDate = '2014-12-31'
|
# startDate = '2015-01-11'
|
||||||
endDate = '2015-01-05'
|
# endDate = '2015-01-17'
|
||||||
|
startDate = '2015-01-23'
|
||||||
|
endDate = '2015-01-23'
|
||||||
|
# endDate = '2015-01-28'
|
||||||
|
|
||||||
|
startEvent = eventFunnel[0]
|
||||||
|
endEvent = eventFunnel[-1]
|
||||||
|
|
||||||
print("Requesting data for {0} to {1}".format(startDate, endDate))
|
print("Requesting data for {0} to {1}".format(startDate, endDate))
|
||||||
data = api.request(['export'], {
|
data = api.request(['export'], {
|
||||||
'event' : ['Started Level', 'Saw Victory'],
|
'event' : eventFunnel,
|
||||||
'from_date' : startDate,
|
'from_date' : startDate,
|
||||||
'to_date' : endDate
|
'to_date' : endDate
|
||||||
})
|
})
|
||||||
|
|
||||||
levelRates = {}
|
|
||||||
|
# Map ordering: level, user, event, day
|
||||||
|
userDataMap = {}
|
||||||
lines = data.split('\n')
|
lines = data.split('\n')
|
||||||
print "Received %d entries" % len(lines)
|
print "Received %d entries" % len(lines)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
|
@ -43,40 +57,102 @@ if __name__ == '__main__':
|
||||||
if len(line) is 0: continue
|
if len(line) is 0: continue
|
||||||
eventData = json.loads(line)
|
eventData = json.loads(line)
|
||||||
eventName = eventData['event']
|
eventName = eventData['event']
|
||||||
if not eventName in ['Started Level', 'Saw Victory']:
|
if not eventName in eventFunnel:
|
||||||
print 'Unexpected event ' + eventName
|
print 'Unexpected event ' + eventName
|
||||||
break
|
break
|
||||||
|
if not 'properties' in eventData: continue
|
||||||
properties = eventData['properties']
|
properties = eventData['properties']
|
||||||
|
if not 'distinct_id' in properties: continue
|
||||||
|
user = properties['distinct_id']
|
||||||
|
if not 'time' in properties: continue
|
||||||
|
time = properties['time']
|
||||||
|
pst = datetime.fromtimestamp(int(properties['time']))
|
||||||
|
utc = pst + timedelta(0, 8 * 60 * 60)
|
||||||
|
dateCreated = utc.isoformat()
|
||||||
|
day = dateCreated[0:10]
|
||||||
|
if day < startDate or day > endDate:
|
||||||
|
print "Skipping {0}".format(day)
|
||||||
|
continue
|
||||||
|
|
||||||
if 'levelID' in properties:
|
if 'levelID' in properties:
|
||||||
levelID = properties['levelID']
|
level = properties['levelID']
|
||||||
elif 'level' in properties:
|
elif 'level' in properties:
|
||||||
levelID = properties['level'].lower().replace(' ', '-')
|
level = properties['level'].lower().replace(' ', '-')
|
||||||
else:
|
else:
|
||||||
print("Unkonwn levelID for", eventName)
|
print("Unkonwn level for", eventName)
|
||||||
print(properties)
|
print(properties)
|
||||||
break
|
break
|
||||||
if not levelID in levelRates:
|
|
||||||
levelRates[levelID] = {'started': 0, 'finished': 0}
|
if not level in targetLevels:
|
||||||
if eventName == 'Started Level':
|
continue
|
||||||
levelRates[levelID]['started'] += 1
|
|
||||||
elif eventName == 'Saw Victory':
|
# print level
|
||||||
levelRates[levelID]['finished'] += 1
|
|
||||||
else:
|
if not level in userDataMap: userDataMap[level] = {}
|
||||||
print("Unknown event name", eventName)
|
if not user in userDataMap[level]: userDataMap[level][user] = {}
|
||||||
print(eventData)
|
if not eventName in userDataMap[level][user] or userDataMap[level][user][eventName] > day:
|
||||||
break
|
userDataMap[level][user][eventName] = day
|
||||||
except:
|
except:
|
||||||
print "Unexpected error:", sys.exc_info()[0]
|
print "Unexpected error:", sys.exc_info()[0]
|
||||||
print line
|
print line
|
||||||
break
|
break
|
||||||
|
|
||||||
# print(levelRates)
|
# print(userDataMap)
|
||||||
for levelID in levelRates:
|
|
||||||
started = levelRates[levelID]['started']
|
levelFunnelData = {}
|
||||||
finished = levelRates[levelID]['finished']
|
for level in userDataMap:
|
||||||
# if not levelID == 'endangered-burl':
|
for user in userDataMap[level]:
|
||||||
# continue
|
funnelStartDay = None
|
||||||
|
for event in userDataMap[level][user]:
|
||||||
|
day = userDataMap[level][user][event]
|
||||||
|
if not level in levelFunnelData: levelFunnelData[level] = {}
|
||||||
|
if not day in levelFunnelData[level]: levelFunnelData[level][day] = {}
|
||||||
|
if not event in levelFunnelData[level][day]: levelFunnelData[level][day][event] = 0
|
||||||
|
if eventFunnel[0] == event:
|
||||||
|
levelFunnelData[level][day][event] += 1
|
||||||
|
funnelStartDay = day
|
||||||
|
break
|
||||||
|
|
||||||
|
if funnelStartDay:
|
||||||
|
for event in userDataMap[level][user]:
|
||||||
|
if not event in levelFunnelData[level][funnelStartDay]:
|
||||||
|
levelFunnelData[level][funnelStartDay][event] = 0
|
||||||
|
if not eventFunnel[0] == event:
|
||||||
|
levelFunnelData[level][funnelStartDay][event] += 1
|
||||||
|
for i in range(1, len(eventFunnel)):
|
||||||
|
event = eventFunnel[i]
|
||||||
|
if not event in levelFunnelData[level][funnelStartDay]:
|
||||||
|
levelFunnelData[level][funnelStartDay][event] = 0
|
||||||
|
|
||||||
|
# print(levelFunnelData)
|
||||||
|
|
||||||
|
totals = {}
|
||||||
|
for level in levelFunnelData:
|
||||||
|
for day in levelFunnelData[level]:
|
||||||
|
if startEvent in levelFunnelData[level][day]:
|
||||||
|
started = levelFunnelData[level][day][startEvent]
|
||||||
|
else:
|
||||||
|
started = 0
|
||||||
|
if endEvent in levelFunnelData[level][day]:
|
||||||
|
finished = levelFunnelData[level][day][endEvent]
|
||||||
|
else:
|
||||||
|
finished = 0
|
||||||
|
if not level in totals: totals[level] = {}
|
||||||
|
if not startEvent in totals[level]: totals[level][startEvent] = 0
|
||||||
|
if not endEvent in totals[level]: totals[level][endEvent] = 0
|
||||||
|
totals[level][startEvent] += started
|
||||||
|
totals[level][endEvent] += finished
|
||||||
|
if started > 0:
|
||||||
|
print("{0}\t{1}\t{2}\t{3}\t{4}%".format(level, day, started, finished, float(finished) / started * 100))
|
||||||
|
else:
|
||||||
|
print("{0}\t{1}\t{2}\t{3}\t".format(level, day, started, finished))
|
||||||
|
|
||||||
|
for level in totals:
|
||||||
|
started = totals[level][startEvent]
|
||||||
|
finished = totals[level][endEvent]
|
||||||
if started > 0:
|
if started > 0:
|
||||||
print("{0}\t{1}\t{2}\t{3}%".format(levelID, started, finished, float(finished) / started * 100))
|
print("{0}\t{1}\t{2}\t{3}%".format(level, started, finished, float(finished) / started * 100))
|
||||||
else:
|
else:
|
||||||
print("{0}\t{1}\t{2}".format(levelID, started, finished))
|
print("{0}\t{1}\t{2}\t".format(level, started, finished))
|
||||||
|
|
||||||
|
print("Script runtime: {0}".format(datetime.now() - scriptStart))
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
|
|
||||||
// TODO: Why do a small number of 'Started level' not have properties.levelID set?
|
// TODO: Why do a small number of 'Started level' not have properties.levelID set?
|
||||||
|
|
||||||
// TODO: spot check the data: NaN, only some 0.0 dates, etc.
|
// TODO: Fix addPlaytimeAverages() and addUserCodeProblemCounts()
|
||||||
// TODO: exclude levels with no interesting data?
|
// TODO: getLevelFunnelData() outputs different data structure now.
|
||||||
|
|
||||||
var startTime = new Date();
|
var startTime = new Date();
|
||||||
|
|
||||||
|
@ -20,12 +20,22 @@ var today = new Date();
|
||||||
today = today.toISOString().substr(0, 10);
|
today = today.toISOString().substr(0, 10);
|
||||||
print("Today is " + today);
|
print("Today is " + today);
|
||||||
|
|
||||||
var todayMinus6 = new Date();
|
// var todayMinus6 = new Date();
|
||||||
todayMinus6.setUTCDate(todayMinus6.getUTCDate() - 6);
|
// todayMinus6.setUTCDate(todayMinus6.getUTCDate() - 6);
|
||||||
var startDate = todayMinus6.toISOString().substr(0, 10) + "T00:00:00.000Z";
|
// var startDate = todayMinus6.toISOString().substr(0, 10) + "T00:00:00.000Z";
|
||||||
print("Start date is " + startDate)
|
// startDate = "2015-01-23T00:00:00.000Z";
|
||||||
// startDate = "2015-01-02T00:00:00.000Z";
|
// print("Start date is " + startDate)
|
||||||
// var endDate = "2015-01-09T00:00:00.000Z";
|
// var endDate = "2015-01-24T00:00:00.000Z";
|
||||||
|
// print("End date is " + endDate)
|
||||||
|
|
||||||
|
var levelCompletionFunnel = ['Started Level', 'Saw Victory'];
|
||||||
|
var dataStartDay = "2015-01-15";
|
||||||
|
var startDay = "2015-01-23";
|
||||||
|
var endDay = "2015-01-24";
|
||||||
|
print(startDay + " to " + endDay);
|
||||||
|
print("Data start day " + dataStartDay);
|
||||||
|
|
||||||
|
var targetLevels = ['dungeons-of-kithgard'];
|
||||||
|
|
||||||
function objectIdWithTimestamp(timestamp)
|
function objectIdWithTimestamp(timestamp)
|
||||||
{
|
{
|
||||||
|
@ -38,72 +48,139 @@ function objectIdWithTimestamp(timestamp)
|
||||||
return constructedObjectId
|
return constructedObjectId
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCompletionRates() {
|
function getLevelFunnelData(startDay, endDay, eventFunnel) {
|
||||||
print("Getting completion rates...");
|
// Copied from insertPerDayAnalytics.js
|
||||||
var queryParams = {
|
if (!startDay || !eventFunnel || eventFunnel.length === 0) return {};
|
||||||
$and: [
|
|
||||||
{_id: {$gte: objectIdWithTimestamp(ISODate(startDate))}},
|
// var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
|
||||||
{$or: [ {"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}
|
var startObj = objectIdWithTimestamp(ISODate(dataStartDay + "T00:00:00.000Z"));
|
||||||
]
|
var endObj = objectIdWithTimestamp(ISODate(endDay + "T00:00:00.000Z"));
|
||||||
};
|
var queryParams = {$and: [{_id: {$gte: startObj}},{_id: {$lt: endObj}},{"event": {$in: eventFunnel}}]};
|
||||||
|
// var queryParams = {$and: [{user: ObjectId("539c630f30a67c3b05d98d95")},{_id: {$gte: startObj}},{_id: {$lt: endObj}},{"event": {$in: eventFunnel}}]};
|
||||||
var cursor = db['analytics.log.events'].find(queryParams);
|
var cursor = db['analytics.log.events'].find(queryParams);
|
||||||
|
|
||||||
// <level><date><data>
|
// Map ordering: level, user, event, day
|
||||||
var levelData = {};
|
var recordCount = 0;
|
||||||
|
var duplicates = {};
|
||||||
|
var levelEventUserDayMap = {};
|
||||||
|
var levelUserEventDayMap = {};
|
||||||
while (cursor.hasNext()) {
|
while (cursor.hasNext()) {
|
||||||
|
recordCount++;
|
||||||
var doc = cursor.next();
|
var doc = cursor.next();
|
||||||
var created = doc.created.toISOString().substring(0, 10);
|
var created = doc._id.getTimestamp().toISOString();
|
||||||
|
var day = created.substring(0, 10);
|
||||||
var event = doc.event;
|
var event = doc.event;
|
||||||
var properties = doc.properties;
|
var properties = doc.properties;
|
||||||
|
var user = doc.user;
|
||||||
var level;
|
var level;
|
||||||
|
|
||||||
|
// TODO: Switch to properties.levelID for 'Saw Victory'
|
||||||
if (event === 'Saw Victory' && properties.level) level = properties.level.toLowerCase().replace(/ /g, '-');
|
if (event === 'Saw Victory' && properties.level) level = properties.level.toLowerCase().replace(/ /g, '-');
|
||||||
else if (properties.levelID) level = properties.levelID
|
else if (properties.levelID) level = properties.levelID
|
||||||
else continue
|
else continue
|
||||||
var user = doc.user;
|
|
||||||
|
// if (targetLevels.indexOf(level) < 0) continue;
|
||||||
|
|
||||||
|
// print(day + " " + created);
|
||||||
|
// print(JSON.stringify(doc, null, 2));
|
||||||
|
|
||||||
if (level.length > longestLevelName) longestLevelName = level.length;
|
if (level.length > longestLevelName) longestLevelName = level.length;
|
||||||
|
|
||||||
if (!levelData[level]) levelData[level] = {};
|
if (!levelUserEventDayMap[level]) levelUserEventDayMap[level] = {};
|
||||||
if (!levelData[level][created]) levelData[level][created] = {};
|
if (!levelUserEventDayMap[level][user]) levelUserEventDayMap[level][user] = {};
|
||||||
if (!levelData[level][created]['started']) levelData[level][created]['started'] = {};
|
if (levelUserEventDayMap[level][user][event]) {
|
||||||
if (!levelData[level][created]['finished']) levelData[level][created]['finished'] = {}
|
if (!duplicates[event]) duplicates[event] = 0;
|
||||||
if (event === 'Started Level') levelData[level][created]['started'][user] = true;
|
duplicates[event]++;
|
||||||
else levelData[level][created]['finished'][user] = true;
|
}
|
||||||
|
if (!levelUserEventDayMap[level][user][event] || levelUserEventDayMap[level][user][event].localeCompare(day) > 0) {
|
||||||
|
// if (!levelUserEventDayMap[level][user][event] || day.localeCompare(levelUserEventDayMap[level][user][event]) > 0) {
|
||||||
|
// day is earlier than levelUserEventDayMap[level][user][event]
|
||||||
|
levelUserEventDayMap[level][user][event] = day;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!levelEventUserDayMap[level]) levelEventUserDayMap[level] = {};
|
||||||
|
if (!levelEventUserDayMap[level][event]) levelEventUserDayMap[level][event] = {};
|
||||||
|
if (!levelEventUserDayMap[level][event][user] || levelEventUserDayMap[level][event][user].localeCompare(day) > 0) {
|
||||||
|
levelEventUserDayMap[level][event][user] = day;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// print("Records: " + recordCount);
|
||||||
|
// print("Duplicates");
|
||||||
|
// print(JSON.stringify(duplicates, null, 2));
|
||||||
longestLevelName += 2;
|
longestLevelName += 2;
|
||||||
|
|
||||||
var levelRates = [];
|
// Data: level, day, event
|
||||||
for (level in levelData) {
|
var noStartDayUsers = [];
|
||||||
var dateData = [];
|
var levelFunnelData = {};
|
||||||
var dateIndex = 0;
|
for (level in levelUserEventDayMap) {
|
||||||
for (created in levelData[level]) {
|
for (user in levelUserEventDayMap[level]) {
|
||||||
var started =
|
|
||||||
dateData.push({
|
// Find first event date
|
||||||
level: level,
|
var funnelStartDay = null;
|
||||||
created: created,
|
for (event in levelUserEventDayMap[level][user]) {
|
||||||
started: Object.keys(levelData[level][created]['started']).length,
|
var day = levelUserEventDayMap[level][user][event];
|
||||||
finished: Object.keys(levelData[level][created]['finished']).length
|
if (day.localeCompare(startDay) < 0) {
|
||||||
});
|
// day earlier than startDay
|
||||||
if (dates.length === dateIndex) dates.push(created.substring(5));
|
continue;
|
||||||
dateIndex++;
|
}
|
||||||
|
if (!levelFunnelData[level]) levelFunnelData[level] = {};
|
||||||
|
if (!levelFunnelData[level][day]) levelFunnelData[level][day] = {};
|
||||||
|
if (!levelFunnelData[level][day][event]) levelFunnelData[level][day][event] = 0;
|
||||||
|
if (eventFunnel[0] === event) {
|
||||||
|
// First event gets attributed to current date
|
||||||
|
levelFunnelData[level][day][event]++;
|
||||||
|
funnelStartDay = day;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (funnelStartDay) {
|
||||||
|
// Add remaining funnel steps/events to first step's date
|
||||||
|
for (event in levelUserEventDayMap[level][user]) {
|
||||||
|
if (!levelFunnelData[level][funnelStartDay][event]) levelFunnelData[level][funnelStartDay][event] = 0;
|
||||||
|
if (eventFunnel[0] != event) levelFunnelData[level][funnelStartDay][event]++;
|
||||||
|
}
|
||||||
|
// Zero remaining funnel events
|
||||||
|
for (var i = 1; i < eventFunnel.length; i++) {
|
||||||
|
var event = eventFunnel[i];
|
||||||
|
if (!levelFunnelData[level][funnelStartDay][event]) levelFunnelData[level][funnelStartDay][event] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: calc no start days
|
||||||
|
for (event in levelUserEventDayMap[level][user]) {
|
||||||
|
var day = levelUserEventDayMap[level][user][event];
|
||||||
|
if (day.localeCompare(startDay) < 0) {
|
||||||
|
// day earlier than startDay
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (eventFunnel[0] != event) {
|
||||||
|
noStartDayUsers.push(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
levelRates.push(dateData);
|
|
||||||
}
|
}
|
||||||
// printjson(levelRates);
|
|
||||||
|
|
||||||
levelRates.sort(function(a,b) {return a[0].level < b[0].level ? -1 : 1});
|
// print("No start day count: " + noStartDayUsers.length);
|
||||||
for (levelKey in levelRates) levelRates[levelKey].sort(function(a,b) {return a.created < b.created ? 1 : -1});
|
// for (var i = 0; i < noStartDayUsers.length && i < 50; i++) {
|
||||||
|
// print(noStartDayUsers[i]);
|
||||||
|
// }
|
||||||
|
|
||||||
return levelRates;
|
return levelFunnelData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPlaytimeAverages(levelRates) {
|
function addPlaytimeAverages(startDay, endDay, levelRates) {
|
||||||
print("Getting playtimes...");
|
print("Getting playtimes...");
|
||||||
// printjson(levelRates);
|
var startObj = objectIdWithTimestamp(ISODate(dataStartDay + "T00:00:00.000Z"));
|
||||||
|
var endObj = objectIdWithTimestamp(ISODate(endDay + "T00:00:00.000Z"));
|
||||||
|
// var match = {"$match" : {$and: [{_id: { $gte: startObj}}, {_id: { $lt: endObj}}]}};
|
||||||
var match = {
|
var match = {
|
||||||
"$match" : {
|
"$match" : {
|
||||||
$and: [
|
$and: [
|
||||||
{"created": { $gte: ISODate(startDate)}},
|
{_id: { $gte: startObj}},
|
||||||
|
{_id: { $lt: endObj}},
|
||||||
{"state.complete": true},
|
{"state.complete": true},
|
||||||
{"playtime": {$gt: 0}}
|
{"playtime": {$gt: 0}}
|
||||||
]
|
]
|
||||||
|
@ -113,12 +190,12 @@ function addPlaytimeAverages(levelRates) {
|
||||||
"_id" : 0,
|
"_id" : 0,
|
||||||
"levelID" : 1,
|
"levelID" : 1,
|
||||||
"playtime": 1,
|
"playtime": 1,
|
||||||
"created": {"$substr" : ["$created", 0, 10]}
|
"day": {"$substr" : ["$created", 0, 10]}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
var group = {"$group" : {
|
var group = {"$group" : {
|
||||||
"_id" : {
|
"_id" : {
|
||||||
"created" : "$created",
|
"day" : "$day",
|
||||||
"level": "$levelID"
|
"level": "$levelID"
|
||||||
},
|
},
|
||||||
"average" : {
|
"average" : {
|
||||||
|
@ -131,36 +208,38 @@ function addPlaytimeAverages(levelRates) {
|
||||||
var levelPlaytimeData = {};
|
var levelPlaytimeData = {};
|
||||||
while (cursor.hasNext()) {
|
while (cursor.hasNext()) {
|
||||||
var doc = cursor.next();
|
var doc = cursor.next();
|
||||||
var created = doc._id.created;
|
var day = doc._id.day;
|
||||||
var level = doc._id.level;
|
var level = doc._id.level;
|
||||||
if (!levelPlaytimeData[level]) levelPlaytimeData[level] = {};
|
if (!levelPlaytimeData[level]) levelPlaytimeData[level] = {};
|
||||||
levelPlaytimeData[level][created] = doc.average;
|
levelPlaytimeData[level][day] = doc.average;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (levelIndex in levelRates) {
|
for (levelIndex in levelRates) {
|
||||||
for (dateIndex in levelRates[levelIndex]) {
|
for (dateIndex in levelRates[levelIndex]) {
|
||||||
var level = levelRates[levelIndex][dateIndex].level;
|
var level = levelRates[levelIndex][dateIndex].level;
|
||||||
var created = levelRates[levelIndex][dateIndex].created;
|
var day = levelRates[levelIndex][dateIndex].day;
|
||||||
if (levelPlaytimeData[level] && levelPlaytimeData[level][created]) {
|
if (levelPlaytimeData[level] && levelPlaytimeData[level][day]) {
|
||||||
levelRates[levelIndex][dateIndex].averagePlaytime = levelPlaytimeData[level][created];
|
levelRates[levelIndex][dateIndex].averagePlaytime = levelPlaytimeData[level][day];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUserCodeProblemCounts(levelRates) {
|
function addUserCodeProblemCounts(startDay, endDay, levelRates) {
|
||||||
print("Getting user code problem counts...");
|
print("Getting user code problem counts...");
|
||||||
var match = {"$match" : {"created": { $gte: ISODate(startDate)}}};
|
var startObj = objectIdWithTimestamp(ISODate(dataStartDay + "T00:00:00.000Z"));
|
||||||
|
var endObj = objectIdWithTimestamp(ISODate(endDay + "T00:00:00.000Z"));
|
||||||
|
var match = {"$match" : {$and: [{_id: { $gte: startObj}}, {_id: { $lt: endObj}}]}};
|
||||||
|
|
||||||
var proj0 = {"$project": {
|
var proj0 = {"$project": {
|
||||||
"_id" : 0,
|
"_id" : 0,
|
||||||
"levelID" : 1,
|
"levelID" : 1,
|
||||||
"created": {"$substr" : ["$created", 0, 10]}
|
"day": {"$substr" : ["$created", 0, 10]}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
var group = {"$group" : {
|
var group = {"$group" : {
|
||||||
"_id" : {
|
"_id" : {
|
||||||
"created" : "$created",
|
"day" : "$day",
|
||||||
"level": "$levelID"
|
"level": "$levelID"
|
||||||
},
|
},
|
||||||
"count" : {
|
"count" : {
|
||||||
|
@ -173,18 +252,18 @@ function addUserCodeProblemCounts(levelRates) {
|
||||||
var levelPlaytimeData = {};
|
var levelPlaytimeData = {};
|
||||||
while (cursor.hasNext()) {
|
while (cursor.hasNext()) {
|
||||||
var doc = cursor.next();
|
var doc = cursor.next();
|
||||||
var created = doc._id.created;
|
var day = doc._id.day;
|
||||||
var level = doc._id.level;
|
var level = doc._id.level;
|
||||||
if (!levelPlaytimeData[level]) levelPlaytimeData[level] = {};
|
if (!levelPlaytimeData[level]) levelPlaytimeData[level] = {};
|
||||||
levelPlaytimeData[level][created] = doc.count;
|
levelPlaytimeData[level][day] = doc.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (levelIndex in levelRates) {
|
for (levelIndex in levelRates) {
|
||||||
for (dateIndex in levelRates[levelIndex]) {
|
for (dateIndex in levelRates[levelIndex]) {
|
||||||
var level = levelRates[levelIndex][dateIndex].level;
|
var level = levelRates[levelIndex][dateIndex].level;
|
||||||
var created = levelRates[levelIndex][dateIndex].created;
|
var day = levelRates[levelIndex][dateIndex].day;
|
||||||
if (levelPlaytimeData[level] && levelPlaytimeData[level][created]) {
|
if (levelPlaytimeData[level] && levelPlaytimeData[level][day]) {
|
||||||
levelRates[levelIndex][dateIndex].codeProblems = levelPlaytimeData[level][created];
|
levelRates[levelIndex][dateIndex].codeProblems = levelPlaytimeData[level][day];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,60 +272,68 @@ function addUserCodeProblemCounts(levelRates) {
|
||||||
var longestLevelName = -1;
|
var longestLevelName = -1;
|
||||||
var dates = [];
|
var dates = [];
|
||||||
|
|
||||||
var levelRates = getCompletionRates();
|
var levelRates = getLevelFunnelData(startDay, endDay, levelCompletionFunnel);
|
||||||
// addPlaytimeAverages(levelRates);
|
// addPlaytimeAverages(startDay, endDay, levelRates);
|
||||||
// addUserCodeProblemCounts(levelRates);
|
// addUserCodeProblemCounts(startDay, endDay, levelRates);
|
||||||
|
|
||||||
|
// print(JSON.stringify(levelRates, null, 2))
|
||||||
|
|
||||||
// Print out all data
|
// Print out all data
|
||||||
print("Columns: level, day, started, finished, completion rate, average finish playtime, average code problem count");
|
// print("Columns: level, day, started, finished, completion rate, average finish playtime, average code problem count");
|
||||||
|
print("Columns: level, day, started, finished, completion rate");
|
||||||
for (levelKey in levelRates) {
|
for (levelKey in levelRates) {
|
||||||
for (dateKey in levelRates[levelKey]) {
|
for (dateKey in levelRates[levelKey]) {
|
||||||
var created = levelRates[levelKey][dateKey].created;
|
// var day = levelRates[levelKey][dateKey].day;
|
||||||
var level = levelRates[levelKey][dateKey].level;
|
// var level = levelRates[levelKey][dateKey].level;
|
||||||
var started = levelRates[levelKey][dateKey].started;
|
// var started = levelRates[levelKey][dateKey].started;
|
||||||
var finished = levelRates[levelKey][dateKey].finished;
|
// var finished = levelRates[levelKey][dateKey].finished;
|
||||||
|
var started = levelRates[levelKey][dateKey][levelCompletionFunnel[0]] || 0;
|
||||||
|
var finished = levelRates[levelKey][dateKey][levelCompletionFunnel[levelCompletionFunnel.length - 1]] || 0;
|
||||||
var completionRate = started > 0 ? finished / started : 0;
|
var completionRate = started > 0 ? finished / started : 0;
|
||||||
var averagePlaytime = levelRates[levelKey][dateKey].averagePlaytime;
|
// var averagePlaytime = levelRates[levelKey][dateKey].averagePlaytime;
|
||||||
averagePlaytime = averagePlaytime ? Math.round(averagePlaytime) : 0;
|
// averagePlaytime = averagePlaytime ? Math.round(averagePlaytime) : 0;
|
||||||
var averageCodeProblems = levelRates[levelKey][dateKey].codeProblems;
|
// var averageCodeProblems = levelRates[levelKey][dateKey].codeProblems;
|
||||||
averageCodeProblems = averageCodeProblems ? (averageCodeProblems / started).toFixed(2) : 0.0;
|
// averageCodeProblems = averageCodeProblems ? (averageCodeProblems / started).toFixed(2) : 0.0;
|
||||||
var levelSpacer = new Array(longestLevelName - level.length).join(' ');
|
if ((longestLevelName - levelKey.length) < 0)
|
||||||
print(level + levelSpacer + created + "\t" + started + "\t" + finished + "\t" + (completionRate * 100).toFixed(2) + "% " + averagePlaytime + "s " + averageCodeProblems);
|
throw new Error(longestLevelName + " " + levelKey.length);
|
||||||
|
var levelSpacer = new Array(longestLevelName - levelKey.length).join(' ');
|
||||||
|
// print(levelKey + levelSpacer + dateKey + "\t" + started + "\t" + finished + "\t" + (completionRate * 100).toFixed(2) + "% " + averagePlaytime + "s " + averageCodeProblems);
|
||||||
|
print(levelKey + levelSpacer + dateKey + "\t" + started + "\t" + finished + "\t" + (completionRate * 100).toFixed(2) + "%");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print out a nice grid of levels with 7 days of data
|
// Print out a nice grid of levels with 7 days of data
|
||||||
print("Columns: level, completion rate/average playtime/average code problems, completion rate/average playtime/average code problems ...");
|
// print("Columns: level, completion rate/average playtime/average code problems, completion rate/average playtime/average code problems ...");
|
||||||
print(new Array(longestLevelName).join(' ') + dates.join('\t\t'));
|
// print(new Array(longestLevelName).join(' ') + dates.join('\t\t'));
|
||||||
for (levelKey in levelRates) {
|
// for (levelKey in levelRates) {
|
||||||
var hasStarted = false;
|
// var hasStarted = false;
|
||||||
for (dateKey in levelRates[levelKey]) {
|
// for (dateKey in levelRates[levelKey]) {
|
||||||
if (levelRates[levelKey][dateKey].started > 0) {
|
// if (levelRates[levelKey][dateKey].started > 0) {
|
||||||
hasStarted = true;
|
// hasStarted = true;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (!hasStarted) continue;
|
// if (!hasStarted) continue;
|
||||||
|
//
|
||||||
if (levelRates[levelKey].length < 6) continue;
|
// if (levelRates[levelKey].length < 6) continue;
|
||||||
|
//
|
||||||
var level = levelRates[levelKey][0].level;
|
// var level = levelRates[levelKey][0].level;
|
||||||
var levelSpacer = new Array(longestLevelName - level.length).join(' ');
|
// var levelSpacer = new Array(longestLevelName - level.length).join(' ');
|
||||||
var msg = level + levelSpacer;
|
// var msg = level + levelSpacer;
|
||||||
|
//
|
||||||
for (dateKey in levelRates[levelKey]) {
|
// for (dateKey in levelRates[levelKey]) {
|
||||||
var created = levelRates[levelKey][dateKey].created;
|
// var day = levelRates[levelKey][dateKey].day;
|
||||||
var started = levelRates[levelKey][dateKey].started;
|
// var started = levelRates[levelKey][dateKey].started;
|
||||||
var finished = levelRates[levelKey][dateKey].finished;
|
// var finished = levelRates[levelKey][dateKey].finished;
|
||||||
var averagePlaytime = levelRates[levelKey][dateKey].averagePlaytime;
|
// var averagePlaytime = levelRates[levelKey][dateKey].averagePlaytime;
|
||||||
averagePlaytime = averagePlaytime ? Math.round(averagePlaytime) : 0;
|
// averagePlaytime = averagePlaytime ? Math.round(averagePlaytime) : 0;
|
||||||
var averageCodeProblems = levelRates[levelKey][dateKey].codeProblems;
|
// var averageCodeProblems = levelRates[levelKey][dateKey].codeProblems;
|
||||||
averageCodeProblems = averageCodeProblems ? averageCodeProblems / started : 0.0;
|
// averageCodeProblems = averageCodeProblems ? averageCodeProblems / started : 0.0;
|
||||||
var completionRate = started > 0 ? finished / started : 0;
|
// var completionRate = started > 0 ? finished / started : 0;
|
||||||
msg += (completionRate * 100).toFixed(2) + "/" + averagePlaytime + "/" + averageCodeProblems.toFixed(2) + "\t";
|
// msg += (completionRate * 100).toFixed(2) + "/" + averagePlaytime + "/" + averageCodeProblems.toFixed(2) + "\t";
|
||||||
}
|
// }
|
||||||
print(msg);
|
// print(msg);
|
||||||
}
|
// }
|
||||||
|
|
||||||
var endTime = new Date();
|
var endTime = new Date();
|
||||||
print("Runtime: " + (endTime - startTime));
|
print("Runtime: " + (endTime - startTime));
|
||||||
|
|
Reference in a new issue