Skip to content

Commit f640bd4

Browse files
committed
Merge pull request #34 from shankari/gamification
Add server and result level stats storage
2 parents d86c12b + d083fa3 commit f640bd4

File tree

9 files changed

+122
-18
lines changed

9 files changed

+122
-18
lines changed

CFC_WebApp/api/cfc_webapp.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import logging
99
from datetime import datetime
10+
import time
1011
# So that we can set the socket timeout
1112
import socket
1213
# For decoding tokens using the google decode URL
@@ -317,12 +318,18 @@ def movesCallback():
317318
@app.hook('before_request')
318319
def before_request():
319320
print("START %s %s %s" % (datetime.now(), request.method, request.path))
321+
request.params.start_ts = time.time()
320322
logging.debug("START %s %s" % (request.method, request.path))
321323

322324
@app.hook('after_request')
323325
def after_request():
324-
print("END %s %s %s" % (datetime.now(), request.method, request.path))
325-
logging.debug("END %s %s" % (request.method, request.path))
326+
msTimeNow = time.time()
327+
duration = msTimeNow - request.params.start_ts
328+
print("END %s %s %s %s %s " % (datetime.now(), request.method, request.path, request.params.user_uuid, duration))
329+
logging.debug("END %s %s %s %s " % (request.method, request.path, request.params.user_uuid, duration))
330+
# Keep track of the time and duration for each call
331+
stats.storeServerEntry(request.params.user_uuid, "%s %s" % (request.method, request.path),
332+
msTimeNow, duration)
326333

327334
# Auth helpers BEGIN
328335
# This should only be used by createUserProfile since we may not have a UUID
@@ -347,6 +354,7 @@ def getUUIDFromToken(token):
347354
return user_uuid
348355

349356
def getUUID(request):
357+
retUUID = None
350358
if skipAuth:
351359
from uuid import UUID
352360
from get_database import get_uuid_db
@@ -355,11 +363,13 @@ def getUUID(request):
355363
else:
356364
# TODO: Figure out what we really want to do here
357365
user_uuid = UUID('{3a307244-ecf1-3e6e-a9a7-3aaf101b40fa}')
366+
retUUID = user_uuid
358367
logging.debug("skipAuth = %s, returning fake UUID %s" % (skipAuth, user_uuid))
359-
return user_uuid
360368
else:
361369
userToken = request.json['user']
362-
return getUUIDFromToken(userToken)
370+
retUUID = getUUIDFromToken(userToken)
371+
request.params.user_uuid = retUUID
372+
return retUUID
363373
# Auth helpers END
364374

365375
# We have see the sockets hang in practice. Let's set the socket timeout = 1

CFC_WebApp/clients/choice/choice.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import logging
22
from get_database import get_section_db
3-
from main import carbon, common
3+
from main import carbon, common, stats
44
from datetime import datetime, time, timedelta
55
from dao.user import User
66
import math
77
import json
88
from uuid import UUID
9+
import time
910
from clients.gamified import gamified
1011
from clients.default import default
1112

1213
# TODO: Consider subclassing to provide client specific user functions
1314
def setCurrView(uuid, newView):
1415
user = User.fromUUID(uuid)
1516
user.setClientSpecificProfileFields({'curr_view': newView})
17+
stats.storeResultEntry(uuid, stats.STAT_VIEW_CHOICE, time.time(), newView)
1618

1719
def getCurrView(uuid):
1820
user = User.fromUUID(uuid)

CFC_WebApp/clients/default/default.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
2-
from main import carbon
2+
from main import carbon, stats
33
from dao.user import User
4+
import time
45
import json
56

67
# BEGIN: Code to get and set client specific fields in the profile (currentScore and previousScore)
@@ -12,7 +13,6 @@ def getCarbonFootprint(user):
1213

1314
def setCarbonFootprint(user, newFootprint):
1415
user.setClientSpecificProfileFields({'carbon_footprint': newFootprint})
15-
1616
# END: Code to get and set client specific fields in the profile (currentScore and previousScore)
1717

1818
def getResult(user_uuid):
@@ -47,6 +47,9 @@ def getResult(user_uuid):
4747
# logging.debug(renderedTemplate)
4848
return renderedTemplate
4949

50+
def getCategorySum(carbonFootprintMap):
51+
return sum(carbonFootprintMap.values())
52+
5053
def runBackgroundTasks(user_uuid):
5154
user = User.fromUUID(user_uuid)
5255
# carbon compare results is a tuple. Tuples are converted to arrays
@@ -57,3 +60,20 @@ def runBackgroundTasks(user_uuid):
5760
carbonCompareResults = carbon.getFootprintCompare(user_uuid)
5861
setCarbonFootprint(user, carbonCompareResults)
5962

63+
(myModeShareCount, avgModeShareCount,
64+
myModeShareDistance, avgModeShareDistance,
65+
myModeCarbonFootprint, avgModeCarbonFootprint,
66+
myModeCarbonFootprintNoLongMotorized, avgModeCarbonFootprintNoLongMotorized, # ignored
67+
myOptimalCarbonFootprint, avgOptimalCarbonFootprint,
68+
myOptimalCarbonFootprintNoLongMotorized, avgOptimalCarbonFootprintNoLongMotorized) = carbonCompareResults
69+
# We only compute server stats in the background, because including them in
70+
# the set call means that they may be invoked when the user makes a call and
71+
# the cached value is None, which would potentially slow down user response time
72+
msNow = time.time()
73+
stats.storeResultEntry(user_uuid, stats.STAT_MY_CARBON_FOOTPRINT, msNow, getCategorySum(myModeCarbonFootprint))
74+
stats.storeResultEntry(user_uuid, stats.STAT_MY_CARBON_FOOTPRINT_NO_AIR, msNow, getCategorySum(myModeCarbonFootprintNoLongMotorized))
75+
stats.storeResultEntry(user_uuid, stats.STAT_MY_OPTIMAL_FOOTPRINT, msNow, getCategorySum(myOptimalCarbonFootprint))
76+
stats.storeResultEntry(user_uuid, stats.STAT_MY_OPTIMAL_FOOTPRINT_NO_AIR, msNow, getCategorySum(myOptimalCarbonFootprintNoLongMotorized))
77+
stats.storeResultEntry(user_uuid, stats.STAT_MY_ALLDRIVE_FOOTPRINT, msNow, getCategorySum(myModeShareDistance) * (278.0/(1609 * 1000)))
78+
stats.storeResultEntry(user_uuid, stats.STAT_MEAN_FOOTPRINT, msNow, getCategorySum(avgModeCarbonFootprint))
79+
stats.storeResultEntry(user_uuid, stats.STAT_MEAN_FOOTPRINT_NO_AIR, msNow, getCategorySum(avgModeCarbonFootprintNoLongMotorized))

CFC_WebApp/clients/gamified/gamified.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import logging
22
from get_database import get_section_db
3-
from main import carbon, common
4-
from datetime import datetime, time, timedelta
3+
from main import carbon, common, stats
4+
from datetime import datetime, timedelta
5+
from datetime import time as dttime
6+
import time
57
from dao.user import User
68
import math
79

@@ -85,14 +87,15 @@ def getScore(user_uuid, start, end):
8587
def updateScore(user_uuid):
8688
today = datetime.now().date()
8789
yesterday = today - timedelta(days = 1)
88-
yesterdayStart = datetime.combine(yesterday, time.min)
89-
todayStart = datetime.combine(today, time.min)
90+
yesterdayStart = datetime.combine(yesterday, dttime.min)
91+
todayStart = datetime.combine(today, dttime.min)
9092

9193
user = User.fromUUID(user_uuid)
9294
(discardedScore, prevScore) = getStoredScore(user)
9395
newScore = prevScore + getScore(user_uuid, yesterdayStart, todayStart)
9496
if newScore < 0:
9597
newScore = 0
98+
stats.storeResultEntry(user_uuid, stats.STAT_GAME_SCORE, time.time(), newScore)
9699
setScores(user, prevScore, newScore)
97100

98101
def getLevel(score):

CFC_WebApp/get_database.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ def get_client_stats_db():
6161
ClientStats = current_db.Stage_client_stats
6262
return ClientStats
6363

64+
def get_server_stats_db():
65+
current_db=MongoClient().Stage_database
66+
ServerStats = current_db.Stage_server_stats
67+
return ServerStats
68+
69+
def get_result_stats_db():
70+
current_db=MongoClient().Stage_database
71+
ResultStats = current_db.Stage_result_stats
72+
return ResultStats
73+
6474
def get_db():
6575
current_db=MongoClient('localhost').Stage_database
6676
return current_db

CFC_WebApp/main/get_database.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ def get_client_stats_db():
6262
ClientStats = current_db.Stage_client_stats
6363
return ClientStats
6464

65+
def get_server_stats_db():
66+
current_db=MongoClient().Stage_database
67+
ServerStats = current_db.Stage_server_stats
68+
return ServerStats
69+
70+
def get_result_stats_db():
71+
current_db=MongoClient().Stage_database
72+
ResultStats = current_db.Stage_result_stats
73+
return ResultStats
74+
6575
def get_db():
6676
current_db=MongoClient('localhost').Stage_database
6777
return current_db

CFC_WebApp/main/stats.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
import logging
22
import time
3-
from get_database import get_client_stats_db
3+
from get_database import get_client_stats_db, get_server_stats_db, get_result_stats_db
44

5+
STAT_TRIP_MGR_PCT_SHOWN = "tripManager.pctShown"
6+
STAT_MY_CARBON_FOOTPRINT = "footprint.my_carbon"
7+
STAT_MY_CARBON_FOOTPRINT_NO_AIR = "footprint.my_carbon.no_air"
8+
STAT_MY_OPTIMAL_FOOTPRINT = "footprint.optimal"
9+
STAT_MY_OPTIMAL_FOOTPRINT_NO_AIR = "footprint.optimal.no_air"
10+
STAT_MY_ALLDRIVE_FOOTPRINT = "footprint.alldrive"
11+
STAT_MEAN_FOOTPRINT = "footprint.mean"
12+
STAT_MEAN_FOOTPRINT_NO_AIR = "footprint.mean.no_air"
13+
STAT_GAME_SCORE = "game.score"
14+
STAT_VIEW_CHOICE = "view.choice"
15+
16+
# Store client measurements (currently into the database, but maybe in a log
17+
# file in the future). The format of the stats received from the client is very
18+
# similar to the input to SMAP, to make it easier to store them in a SMAP
19+
# database in the future
520
def setClientMeasurements(user, reportedVals):
621
logging.info("Received %d client keys and %d client readings for user %s" % (len(reportedVals['Readings']),
722
getClientMeasurementCount(reportedVals['Readings']), user))
@@ -12,10 +27,29 @@ def setClientMeasurements(user, reportedVals):
1227
for key in stats:
1328
values = stats[key]
1429
for value in values:
15-
currEntry = createEntry(user, key, value[0], value[1])
16-
# Add the os and app versions from the metadata dict
17-
currEntry.update(metadata)
18-
get_client_stats_db().insert(currEntry)
30+
storeClientEntry(user, key, value[0], value[1], metadata)
31+
32+
def storeClientEntry(user, key, ts, reading, metadata):
33+
logging.debug("storing client entry for user %s, key %s at timestamp %s" % (user, key, ts))
34+
currEntry = createEntry(user, key, ts, reading)
35+
# Add the os and app versions from the metadata dict
36+
currEntry.update(metadata)
37+
get_client_stats_db().insert(currEntry)
38+
39+
# server measurements will call this directly since there's not much point in
40+
# batching in a different location and then making a call here since it runs on
41+
# the same server. Might change if we move engagement stats to a different server
42+
# Note also that there is no server metadata since we currently have no
43+
# versioning on the server. Should probably add some soon
44+
def storeServerEntry(user, key, ts, reading):
45+
logging.debug("storing server entry %s for user %s, key %s at timestamp %s" % (reading, user, key, ts))
46+
currEntry = createEntry(user, key, ts, reading)
47+
get_server_stats_db().insert(currEntry)
48+
49+
def storeResultEntry(user, key, ts, reading):
50+
logging.debug("storing result entry %s for user %s, key %s at timestamp %s" % (reading, user, key, ts))
51+
currEntry = createEntry(user, key, ts, reading)
52+
get_result_stats_db().insert(currEntry)
1953

2054
def getClientMeasurementCount(readings):
2155
retSum = 0

CFC_WebApp/main/tripManager.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,19 @@ def queryUnclassifiedSections(uuid):
123123
{'user_id':user_uuid},
124124
{'confirmed_mode': ''},
125125
{ 'type': 'move' }]})
126-
logging.debug('Unsec.count = %s' % unclassifiedSections.count())
127-
logging.debug('Total Unsec.count = %s' % totalUnclassifiedSections.count())
126+
127+
unclassifiedSectionCount = unclassifiedSections.count()
128+
totalUnclassifiedSectionCount = totalUnclassifiedSections.count()
129+
130+
logging.debug('Unsec.count = %s' % unclassifiedSectionCount)
131+
logging.debug('Total Unsec.count = %s' % totalUnclassifiedSectionCount)
132+
# Keep track of what percent of sections are stripped out.
133+
# Sections can be stripped out for various reasons:
134+
# - they are too old
135+
# - they have enough confidence that above the magic threshold (90%) AND
136+
# the client has requested stripping out
137+
stats.storeEntry(user_uuid, "stats.TRIP_MGR_PCT_SHOWN", time.time(),
138+
float(unclassifiedSectionCount)/totalUnclassifiedSectionCount)
128139
return unclassifiedSections
129140

130141
def getUnclassifiedSections(uuid):

CFC_WebApp/tests/client_tests/TestDefault.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ def testCarbonFootprintStore(self):
2626
default.setCarbonFootprint(user, dummyCarbonFootprint)
2727
# recall that pymongo converts tuples to lists somewhere down the line
2828
self.assertEquals(default.getCarbonFootprint(user), list(dummyCarbonFootprint))
29+
30+
def testGetCategorySum(self):
31+
calcSum = default.getCategorySum({'walking': 1, 'cycling': 2, 'bus': 3, 'train': 4, 'car': 5})
32+
self.assertEqual(calcSum, 15)
2933

3034
if __name__ == '__main__':
3135
unittest.main()

0 commit comments

Comments
 (0)